【算法】页面置换算法FIFO、LRU和LFU的概述以及实现方式

页面置换算法,我们最常用的页面置换算法包括FIFO先来先服务,LRU最近最久未被使用,LFU最近最少被使用以及我们的时钟置换算法。

一、FIFO算法——先来先服务

1、简述FIFO算法

FIFO算法是我们比较简单的置换算法,就是先来先服务或者说是先进先出。也就是说在进行页面置换的时候,最先来的那个会被最先置换出去。先进入的指令先完成并引退,跟着才执行第二条指令。

2FIFO算法的简单实现

FIFO算法的简单实现:可以通过维护一个链表结构去存储当前调入的页面;将最先进入的页面维护在链表的最前,最后进入的页面维护在链表的最后;这样,当发生缺页中断时,需要进行置换的时候,淘汰表头的页面并将新调入的页面加到链表的尾部;

         当然除了链表以外我们还可以采用数组或者队列等来进行实现。

3、FIFO算法的特点

(1)FIFO算法实现简单,易于理解易于编程。FIFO算法实现简单,无须硬件支持,只需要用循环数组管理物理块即可。

(2)FIFO算法可能会出现Belady现象。也就是在FIFO算法中,如果未分配够一个进程所要求的页面,有时就会出现分配的页面数增多,却也率反而增加Belady现象。

(3)FIFO算法可能会置换调重要的页面,其效率不高。

(4)在FIFO算法可能中会导致多次的页面置换。当页面置换的时间大于所要操作的时间的时候,这时候其效率就会很低。当其不停的进行页面置换的时候会出现大量的系统抖动现象。

二、LRU算法——最近最久未被使用

1、简述LRU算法

LRU算法是最近最久未被使用的一种置换算法。也就是说LRU是向前查看。在进行页面置换的时候,查找到当前最近最久未被使用的那个页面,将其剔除在内存中,并将新来的页面加载进来。

2、LRU算法的实现

LRU的实现就相对于FIFO的实现复杂一点。我们可以采用哈希映射和链表相结合。

方法一:数组

用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。

数组:查询比较快,但是对于增删来说是一个不是一个好的选择;

方法二:链表

利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。

链表:查询比较慢,但是对于增删来说十分方便O(1)时间复杂度内搞定;

方法三:hash+链表:保证了查询和增删的时间复杂度O(1)

因此,我们具体介绍这种实现方法。

具体过程描述:首先对于链表结构的描述,链表中包含我们的key,valueyu,以及我们的prev和pnext前驱和后继域。Hashmap我们用来作为一个检索表,它的检索时间复杂读最优可到O(1),也就是我们的get过程的时间复杂度可到O(1);那么,通过双向链表进行set的过程时间复杂度也可到O(1)。

我们可以通过哈希映射快速检索到来的页面是否已经存在,如果已经存在的活,直接映射到我们的链表中,将链表中该节点从当前位置移除直接插入到链表的头部,这样就能保证链表的尾部是我们最近最久未被使用的页面;头部是我们最近刚使用过的页面。

Get:是获取元素的操作;通过hashmap可以直接映射到链表中,查看是否有命中的元素,如果有,将元素从当前位置删除,并且再将其插入到首位置;如果没有,返回NULL;

Set:是插入元素的操作;插入前先通过hashmap查看该元素是否存在,如果存在同上操作(先从该位置移除,再将其插入到链表首部);如果不存在即没有命中,将元素插入到链表的首部,同时判断容量是否达到最大,如果达到最大,将链表尾部元素进行删除;hashmap同样也要进行更新删除;

(注意:每次进行删除和插入操作的时候都需要对hashmap表中的key数据进行调整;而value值保持不变,这样就能正确映射。)

3、LRU算法的特点

LRU是一种页面置换算法,在对于内存中但是又不用的数据块,叫做LRU,操作系统会根据那些数据属于LRU而将其移出内存而腾出空间来加载另外的数据;

如果进程被调度,该进程需要使用的外存页(数据)不存在于数据块中,这个现象就叫做缺页。如果这个数据此时不在,就会将这个数据重新加入到数据块首部。

数据块插入与剔除:每次有新数据到来时,会将其放入数据块首部,当数据每次被访问时,会将其放入数据块首部,当数据每次被访问时,将这个数据插入数据块的首部;如果数据块满了,每次新进的数据都会将数据块尾部的数据挤出数据块。

三、LFU算法——最近最少未被使用

1、LFU算法简述

LFU是最近最少未被使用。也就是说当页面满时,需要进行页面置换的时候,所采取的措施是在缓存队列中找到最近使用次数最少的页面,将其剔除出去。将新的页面加载到页面缓存队列中。也就是说在LFU中,需要记录每个页面被访问的次数。

如上图所示,在内存缓冲满的时候,2的访问次数是最少的,所以此时进行页面置换的时候,就将2置换出去,加载新来的页面。

2、LFU的实现方式

方法一:hashmap(存储数据项在数组中的对应关系)+数组(存储数据项+对应的引用计数)

为了能够淘汰最少使用的数据,因此LFU算法最简单的一种设计思路就是:利用一个数组存储数据项,用hashmap存储每个数据项在数组中对应的位置,然后为每个数据项设计一个访问频次,当数据项被命中时,访问频次自增,在淘汰的时候淘汰访问 频次最少的数据。这样一来的话,在插入数据和访问数据的时候都能达到O(1)的时间复杂度,在淘汰数据的时候,通过选择算法得到应该淘汰的数据项在数组中的索引,并将该索引位置的内容替换为新来的数据内容即可,这样的话,淘汰数据的操作时间复杂度为O(n)。

方法二:hashmap+小顶堆

小顶堆是利用页面的访问次数进行构建;每次最少的访问次数的页面在小顶堆的堆根;利用hashmap进行映射,插入和删除的操作都是O(logN);

方法三:二级哈希映射

往后会继续补充

  • 6
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: LFU算法代码: ``` class LFUCache { public: LFUCache(int capacity) { cap = capacity; minFreq = ; } int get(int key) { if (cache.count(key) == ) { return -1; } freq[cache[key].second].erase(iter[key]); cache[key].second++; freq[cache[key].second].push_back(key); iter[key] = --freq[cache[key].second].end(); if (freq[minFreq].size() == ) { minFreq++; } return cache[key].first; } void put(int key, int value) { if (cap <= ) { return; } if (get(key) != -1) { cache[key].first = value; return; } if (cache.size() >= cap) { cache.erase(freq[minFreq].front()); iter.erase(freq[minFreq].front()); freq[minFreq].pop_front(); } cache[key] = {value, 1}; freq[1].push_back(key); iter[key] = --freq[1].end(); minFreq = 1; } private: int cap, minFreq; unordered_map<int, pair<int, int>> cache; unordered_map<int, list<int>> freq; unordered_map<int, list<int>::iterator> iter; }; ``` LRU算法代码: ``` class LRUCache { public: LRUCache(int capacity) { cap = capacity; } int get(int key) { if (cache.count(key) == ) { return -1; } auto it = cache[key]; int val = it->second; lru.erase(it); lru.push_front({key, val}); cache[key] = lru.begin(); return val; } void put(int key, int value) { if (cap <= ) { return; } if (get(key) != -1) { cache[key]->second = value; return; } if (cache.size() >= cap) { int delKey = lru.back().first; lru.pop_back(); cache.erase(delKey); } lru.push_front({key, value}); cache[key] = lru.begin(); } private: int cap; list<pair<int, int>> lru; unordered_map<int, list<pair<int, int>>::iterator> cache; }; ``` FIFO算法代码: ``` class FIFOCache { public: FIFOCache(int capacity) { cap = capacity; } int get(int key) { if (cache.count(key) == ) { return -1; } return cache[key]; } void put(int key, int value) { if (cap <= ) { return; } if (cache.size() >= cap && cache.count(key) == ) { int delKey = fifo.front(); fifo.pop(); cache.erase(delKey); } if (cache.count(key) == ) { fifo.push(key); } cache[key] = value; } private: int cap; queue<int> fifo; unordered_map<int, int> cache; }; ``` ### 回答2: 页面置换算法是操作系统中用于管理内存的一种重要策略,在不足内存的情况下将内存中不常用的面暂时保存到辅助存储器中,从而保证计算机的正常运行。在实际的操作系统中,有许多不同的页面置换算法,其中比较常用的有LFULRUFIFO算法。 其中,LFU(Least Frequently Used)算法是根据面的历史使用次数来选择将哪一个面换出内存。在使用过程中,每个面都有一个使用计数器,每次访问该面时就会将计数器的值增加。当需要淘汰一个面时,就选择使用次数最少的那个面换出内存。 LRU(Least Recently Used)算法,则是根据面上次使用的时间来选择淘汰哪一个面,即淘汰最长时间未被访问的面。在实现中,可以使用链表或者栈来存储面的访问时间,每次访问面时,就将该面移到链表或者栈的顶部。当需要淘汰面时,就选择最底部的那个面。 FIFO(First In First Out)算法则是根据面进入内存的顺序来选择淘汰那一个面。在使用过程中,每个面都有一个进入时间,当需要淘汰一个面时,就选择最早进入内存的那个面进行淘汰。 这些算法都有其特点和优缺点,实际使用时需要根据不同的场景选择合适的算法。例如,LFU算法适用于对访问次数进行较为敏感的场景,LRU算法适用于需要保证近期访问面能够被频繁使用的场景,FIFO算法适用于对访问顺序较为敏感的场景。 以下是三种算法的示例代码: LFU算法: ``` void lfu_replace(int *mem, int *usage, int num_pages, int curr_page) { int min = usage[0], min_idx = 0; for (int i = 1; i < num_pages; i++) { if (usage[i] < min) { min = usage[i]; min_idx = i; } } mem[min_idx] = curr_page; usage[min_idx] = 1; } ``` LRU算法: ``` void lru_replace(int *mem, int num_pages, int curr_page) { int i, j, min; for (i = 0; i < num_pages; i++) { if (mem[i] == curr_page) { break; } } if (i == num_pages) { // 面不在内存中 for (i = 0, min = mem[0], j = 0; j < num_pages; j++) { if (mem[j] < min) { min = mem[j]; i = j; } } mem[i] = curr_page; // 替换最久未使用的面 } } ``` FIFO算法: ``` void fifo_replace(int *mem, int num_pages, int curr_page, int *start_idx) { int i; if (mem[*start_idx] == -1) { // 内存未满 mem[*start_idx] = curr_page; (*start_idx)++; } else { // 根据面进入内存的顺序替换面 mem[*start_idx] = curr_page; (*start_idx) = ((*start_idx) + 1) % num_pages; } } ``` ### 回答3: 页面置换算法是内存管理中的重要概念,它决定了当物理内存不足时应该把哪些面置换出去。通常情况下,操作系统会根据一定策略选择要置换的面,而其中最常用的三种算法就是LFU(最不经常使用)、LRU(最近最少使用)和FIFO(先进先出)。 LFU算法实现代码如下: ``` 1. 扫描内存中所有面的访问频率 2. 找到访问频率最低的面,选择该面作为要置换出去的面 3. 如果有多个面访问频率一样,从它们中选择最长时间未访问的面作为被置换面 ``` LFU算法是一个比较优秀的页面置换算法,在保证面缓存高命中率的同时,能够剔除那些不常用的面,从而使得空间利用更为有效。不过,LFU算法的缺点是需要定期扫描内存中的面,这会占用一定的CPU资源。 LRU算法实现代码如下: ``` 1. 维护一个链表,每当一个面被访问,就将该面移动到链表的最前端 2. 当内存不足时,选择链表尾部的面进行置换 ``` LRU算法是一种比较优秀的算法,它能够选择一定时间段内未被访问的面进行缓存置换,从而保证空间的有效利用。不过,LRU算法需要维护一个链表结构,每个面都需要记录它的访问时间戳,这会占用内存空间。 FIFO算法实现代码如下: ``` 1. 将新面按照先进先出的原则加入内存 2. 当内存不足时,选择最早加入内存的面置换出去 ``` FIFO算法是一种较为简单的算法,不需要记录访问频率和时间戳等信息,只需要实现一个队列即可。不过,FIFO算法容易发生“抖动”现象,也就是不断地换入换出同一个面,从而降低了命中率。除此之外,FIFO算法也不够智能化,没有考虑到未来的缓存需求,只是进行简单的先进先出的选择置换。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值