1. 頁面置換演算法之LRU演算法
三種常見的頁面置換演算法:FIFO、LFU、LRU
參考:
緩存演算法(頁面置換演算法)-FIFO、LFU、LRU
LRU(Least Recently Used,最近最少使用)演算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是: 如果一個數據在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小 。也就是說,當限定的空間已存滿數據時,應當把最久沒有被訪問到的數據淘汰。
假設 序列為 4 3 4 2 3 1 4 2
物理塊有3個 則
首輪 4調入內存 4
次輪 3調入內存 3 4
之後 4調入內存 4 3
之後 2調入內存 2 4 3
之後 3調入內存 3 2 4
之後 1調入內存 1 3 2(因為最少使用的是4,所以丟棄4)
之後 4調入內存 4 1 3(原理同上)
最後 2調入內存 2 4 1
如果讓我們設計一個LRU Cache的數據結構,它應該支持兩個操作:
一種是採用數組來存儲每個數據項,再對每個key關聯一個時間戳,在cache中維護一個最大時間戳,其設計要點如下:
另一種是採用hashmap+雙向鏈表的數據結構,其設計要點如下:
對比上一節的兩種設計思路,不難發現,設計1需要為每個key維護一個時間戳,而且set和get操作的時間復雜度都是O(n)。顯而易見,隨著數據量的增大,set和get操作的速度越來越慢。而設計2通過採用hashmap+雙向鏈表,set和get操作的時間復雜度只需O(1),下面給出設計2的具體實現。
運行結果為:
參考:
LRU Cache
LRU原理和Redis實現——一個今日頭條的面試題
2. c語言編寫頁面置換演算法
//熬夜弄出來的,記得加分哦
#include<stdio.h>
void Print(int bc[],int blockCount)
{
for(int i=0;i<blockCount;i++)
{
printf("%d ",bc[i]);
}
printf("\n");
}
bool Travel(int bc[],int blockCount,int x)
{
bool is_found=false;
int i;
for(i=0;i<blockCount;i++)
{
if(bc[i]==x)
{
is_found=true;
break;
}
}
return is_found;
}
void FIFO(int pc[],int bc[],int pageCount,int blockCount)
{
printf("0:FIFO置換演算法\n");
int i;
if(pageCount<=blockCount)
{
printf("缺頁次數為0\n");
printf("缺頁率為0\n");
}
else
{
int noPage=0;
int p=0;
for(i=0;i<pageCount;i++)
{
//printf("引用頁:%d\n",pc[i]);
if(!Travel(bc,blockCount,pc[i]))
{
if(i<blockCount)
{
bc[i]=pc[i];
}
else
{
if(p==blockCount)
{
p=0;
}
bc[p]=pc[i];
p++;
}
noPage++;
//printf("物理塊情況:\n");
//Print(bc,blockCount);
}
//printf("\n");
}
printf("FIFO缺頁次數為:%d\n",noPage);
printf("FIFO缺頁率為:%.2f%%\n",(float)noPage/pageCount*100);
}
}
int FoundMaxNum(int a[],int n)
{
int k,j;
k=a[0];
j=0;
for (int i=0;i<n;i++)
{
if(a[i]>=k)
{
k=a[i];
j=i;
}
}
return j;
}
void LRU(int pc[],int bc[],int pageCount,int blockCount)
{
printf("1:LRU置換演算法\n");
if(pageCount<=blockCount)
{
printf("缺頁次數為0\n");
printf("缺頁率為0\n");
}
else
{
int noPage=0;
int i,j,m;
int bc1[100];
for(i=0;i<blockCount;i++)
{
bc1[i]=0;
}
for(i=0;i<pageCount;i++)
{
// printf("引用頁:%d\n",pc[i]);
if(!Travel(bc,blockCount,pc[i]))
{
if(i<blockCount)
{
bc[i]=pc[i];
for(int p=0;p<=i;p++)
{
bc1[p]++;
}
}
else
{
for(j=0;j<blockCount;j++)
{
bc1[j]++;
}
int k=FoundMaxNum(bc1,blockCount);
bc[k]=pc[i];
bc1[k]=1;
}
noPage++;
//printf("物理快情況:\n");
//Print(bc,blockCount);
}
else if(Travel(bc,blockCount,pc[i]))
{
if(i<blockCount)
{
for(j=0;j<=i;j++)
{
bc1[j]++;
}
for(m=0;m<=i;m++)
{
if(bc[m]==pc[i])
{
break;
}
}
bc1[m]=1;
bc[m]=pc[i];
}
else
{
for(j=0;j<blockCount;j++)
{
bc1[j]++;
}
for(m=0;m<blockCount;m++)
{
if(bc[m]==pc[i])
{
break;
}
}
bc1[m]=1;
bc[m]=pc[i];
}
}
//printf("\n");
}
printf("LRU缺頁次數為:%d\n",noPage);
printf("LRU缺頁率為:%.2f%%\n",(float)noPage/pageCount*100);
}
}
void Optiomal(int pc[],int bc[],int pageCount,int blockCount)
{
printf("2:最佳置換演算法\n");
if(pageCount<=blockCount)
{
printf("缺頁次數為0\n");
printf("缺頁率為0\n");
}
else
{
int noPage=0;
int i,j,k;
for(i=0;i<pageCount;i++)
{
// printf("引用頁:%d\n",pc[i]);
if(!Travel(bc,blockCount,pc[i]))
{
if(i<blockCount)
{
bc[i]=pc[i];
}
else
{
int max=0;
int blockIndex;;
for(j=0;j<blockCount;j++)
{
for(k=i;k<pageCount;k++)
{
if(bc[j]==pc[k])
{
break;
}
}
if(k>=max)
{
max=k;
blockIndex=j;
}
}
bc[blockIndex]=pc[i];
}
noPage++;
//printf("物理快情況:\n");
//Print(bc,blockCount);
}
//printf("\n");
}
printf("OPT缺頁次數為:%d\n",noPage);
printf("OPT缺頁率為:%.2f%%\n",(float)noPage/pageCount*100);
}
}
int main()
{
int pageCount,blockCount,i,pc[100];
printf("輸入頁面數\n");
scanf("%d",&pageCount);
printf("輸入頁面走向\n");
for(i=0;i<pageCount;i++)
{
scanf("%d",&pc[i]);
}
blockCount=3;//物理塊數
int bc1[100];
printf("\n");
FIFO(pc,bc1,pageCount,blockCount);
int bc2[100];
printf("\n");
LRU(pc,bc2,pageCount,blockCount);
int bc3[100];
printf("\n");
Optiomal(pc,bc3,pageCount,blockCount);
return 0;
}
3. 頁面置換演算法
上文說到,請求分頁管理方式中,當需要調入頁面到內存中,但此時內存已滿,就需要從內存中按照一定的置換演算法決定將哪個頁面取出將內存給調入的頁面。本文將介紹幾種頁面置換算方法。
本文內容
演算法思想:每次選擇 淘汰的頁面 將是 以後永不使用 ,或者 在最長時間內不再被訪問的頁面 ,這樣可以保證最低的缺頁率。
舉例說明,假設系統為進程分配了三個內存塊,並考慮到有以下頁面號引用串(會依次訪問這些頁面):7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1
....按照此演算法依次執行,最後的結果如下
結果圖
註:缺頁時未必發生頁面置換,若還有可用的空閑內存空間就不用進行頁面置換。
最佳置換演算法可以保證最低的缺頁率,但是實際上,只有進程執行的過程中才能知道接下來會訪問到的是哪個頁面。操作系統無法提前預判頁面的訪問序列。因此, 最佳置換演算法是無法實現的 。
演算法思想:每次選擇 淘汰的頁面是最早進入內存的頁面。
該演算法很簡單,每次淘汰最在內存中待時間最久的各個,下面分別給出系統為進程分為配三個內存塊和四個內存塊的執行情況圖。訪問序列為3,2,1,0,3,2,4,3,2,1,0,4
分配三個內存塊的情況:
分配四個內存塊的情況:
當為進程分配的物理塊數增大時,缺頁次數不減反增的異常現象稱為 貝萊迪(Belay)異常 。
只有FIFO演算法會產生Belay異常。 另外,FIFO演算法雖然實現簡單,但是該演算法與進程實際運行時的規律不適應。因為先進入的頁面也有可能最經常被訪問。因此, 演算法性能差。
演算法思想: 每次淘汰的頁面是最近最久未使用的頁面。
實現方法:賦予每個頁面對應的頁表項中,用 訪問欄位記錄該頁面自上次被訪問以來所經歷的時間t。 當需要淘汰一個頁面時,選擇現有頁面中t最大的頁面,即最近最久未使用。
舉例說明,加入某系統為某進程分配了四個內存塊,並考慮到有以下頁面號引用串:1,8,1,7,8,2,7,2,1,8,3,8,2,1,3,1,7,1,3,7
這里先直接給出答案
結果圖
最佳置換演算法那性能最好,但無法實現。先進先出置換演算法實現簡單,但是演算法性能差。最近最久未使用置換演算法性能好,是最接近OPT演算法性能的,但是實現起來需要專門的硬體支持,演算法開銷大。 時鍾置換演算法 是一種 性能和開銷均平衡 的演算法。又稱 CLOCK演算法 ,或 最近未用演算法 ( NRU ,Not Recently Used)
簡單CLOCK演算法 演算法思想:為每個頁面設置一個 訪問位 ,再將內存中的頁面都通過 鏈接指針鏈接成一個循環隊列 。當某個頁被訪問時,其訪問位置1.當需要淘汰一個頁面時,只需檢查頁的訪問位。如果是0,就選擇該頁換出;如果是1,暫不換出,將訪問位改為0,繼續檢查下一個頁面,若第一輪掃描中所有的頁面都是1,則將這些頁面的訪問位一次置為0後,再進行第二輪掃描(第二輪掃描中一定會有訪問位為0的頁面,因此簡單的CLOCK演算法選擇一個淘汰頁面最多會經過 兩輪掃描 )。
這個演算法指針在掃描的過程就像時鍾一樣轉圈,才被稱為時鍾置換演算法。
簡單的時鍾置換演算法僅考慮到了一個頁面最近是否被訪問過。事實上,如果淘汰的頁面沒有被修改過,就不需要執行I/O操作寫回外存。 只有淘汰的頁面被修改過時,才需要寫回外存。
因此,除了考慮一個頁面最近有沒有被訪問過之外,操作系統還需要考慮頁面有沒有被修改過。
改進型時鍾置換演算法的 演算法思想 : 在其他在條件相同時,應該優先淘汰沒有被修改過的頁面, 從而來避免I/O操作。
為了方便討論,用(訪問位,修改位)的形式表示各頁面的狀態。如(1,1)表示一個頁面近期被訪問過,且被修改過。
演算法規則 :將所有可能被置換的頁面排成一個循環隊列
由於第二輪已將所有的頁的訪問位都設為0,因此第三輪、第四輪掃描一定會選中一個頁,因此 改進型CLOCK置換演算法最多會進行四輪掃描。
假設系統為進程分配了5個內存塊,某時刻,各個頁的狀態如下圖
如果此時有新的頁要進入內存,開始第一輪掃描就找到了要替換的頁,即最下面的狀態為(0,0)的頁。
某一時刻頁面狀態如下
如果此時有新的頁要進入內存,開始第一輪掃描就發現沒有狀態為(0,0)的頁,第一輪掃描後不修改任何標志位。所以各個頁狀態和上圖一樣。
然後開始第二輪掃描,嘗試找到狀態為(0,1)的頁,並將掃描過後的頁的訪問位設為0,第二輪掃描找到了要替換的頁。
某一時刻頁面狀態如下
第一輪掃描沒有找到狀態為(0,0)的頁,且第一輪掃描不修改任何標志位,所以第一輪掃描後狀態和上圖一致。
然後開始第二輪掃描,嘗試找狀態為(0,1)的頁,也沒有找到,第二輪掃描需要將訪問位設為1,第二輪掃描後,狀態為下圖
某一時刻頁面狀態如下
具體的掃描過程和上面相同,這里只給出最後的結果,如下圖
所以,改進型的CLOCK置換演算法最多需要四輪掃描確定要置換的頁。從上面的分析可以看出,改進型的CLOCK置換演算法
(1) 第一優先順序淘汰的是 最近沒有訪問且沒有修改 的頁面。
(2) 第二優先順序淘汰的是 最近沒有訪問但修改 的頁面。
(3) 第三優先順序淘汰的是 最近訪問但沒有修改 的頁面。
(4) 第四優先順序淘汰的是 最近訪問且修改 的頁面。