概述:

在上一篇《操作系统:基于页面置换算法的缓存原理详解(上)》中,我们主要阐述了FIFO、LRU和Clock页面置换算法。接着上一篇说到的,本文也有三个核心算法要讲解。分别是LFU(Least Frequently Used)、LRU-KMQ(Multi Queue)算法。

本文链接:http://blog.csdn.net/lemon_tree12138/article/details/50475240 --Coding-Naga
                                                                 --转载请注明出处

1.LFU

我们从LFU的英文全称Least Frequently Used中就可以看到,此算法是基于资源被访问的次数来实现的。由于算法很简单,我们就直接给出思路,而逻辑代码就不再展示了(因为下面的LRU-KMQ才是本文的关键)。LFU的原理图可参见图-1.

图-1 LFU置换算法原理图

算法步骤:

(1)当有新资源被访问时,就把这个资源添加到缓存队列的尾部;

(2)当访问一个已经存在的资源时,就把这个资源被访问的次数+1,再上移至合适的位置;

(3)在被访问次数相同的资源集合中,是按照访问时间来排序的;

(4)当新资源加入时,检测到此时队列已满,那么就把队列尾部的资源换出,将新资源添加到队列的尾部。

算法评价:

个人认为此算法并不是一个很好缓存算法,因为它不能很好地反映“用户”在一个比较短的时间里访问资源的走向。

2.LRU-K

LRU算法中存在的问题:

LRU-K算法从名字就可以知道这一定是基于LRU算法的,LRU-K算法是在LRU算法上的一次改进。现在我们分两种情况分别来看看LRU算法的效果。

第一种,假设存在一定量连续的访问,比如说我先访问A资源10次,再访问B资源10次,再访问A资源10次,再访问C资源10次,等等等等。这样我可以大致补上一个画面就是缓存队列是在一个比较小的范围里来回替换,这样就减少换入换出的次数,提高系统的性能,这是第一种情况。

第二种情况就是我们依次访问资源A、B、C、...、Y、Z,再重复n次。并且我们的缓存列队长度比这个循环周期要小。这样,我们的资源就需要不停地换入换出,增加IO操作,效率自然就下来了。这种情况导致的效果也被称着是缓存污染。

LRU-K的优势:

针对上面第二种情况产生的缓存污染,我们做了一个相应地调整——加入了一个新的历史记录的队列。前面LRU算法中,我们是只要访问了某一个资源,那么就把这个资源加入缓存,这样的结果是资源浪费,毕竟缓存队列的资源有限。而在LRU-K算法中,我们不再把只访问一次的资源放入缓存,而是当资源被访问了K次之后,才把这个资源加入到缓存队列中去,并且从历史队列中删除。当资源放入缓存中之后,我们就不用再考虑它的访问次数了。在缓存队列中,我们是以LRU算法来进行更新和淘汰的(对于历史队列可以使用FIFO也可以使用LRU)。

你是不是要问,既然这里加了一个新的历史队列还是要使用LRU算法,那么优势在哪里?而且加入了一个新的队列,也是开销呀,怎么能说还解决了LRU的问题呢?事实上一开始我也有这样的念头,后来一想,我们的历史记录队列只是一个记录数组,我们可以让它的开销很小。这里要怎么做呢?能想到么?

我们知道我们这里要说的资源,可能是一个进程,可能是一个什么其他比较大的对象。那么,如果直接在历史队列中保存这些对象或是进程,并不是一件很划算的事情,不是吗?所以,我们的突破点就是这个历史记录队列能不能尽可能地小?是可以的。我们可以对对象进行Hash成一个整数,这样就可以不用保存原来的对象了,而后面的次数可以使用byte来保存(因为我们可以默认某一个资源在一定时间内,访问的次数不会大得离谱,当然可以使用int或是long,没问题)。如此一来,历史列队就可以做得很小了。如果你对于Hash这一点还不清楚,那么你需要做的是补补你的基础知识了。

LRU-K算法原理图解请下面的图-1.

图-2 LRU-K置换算法原理图

代码实现:

在代码实现的过程,我们有一个理想地假设,那就是我们将添加新资源与访问老资源完全独立开来。添加(offer)资源时默认在两个队列中都不包含,访问的资源总是存在于某一个队列当中。

添加新元素(offer):

public void offer(Object object) {if (histories == null) {throw new NullPointerException();}if (histories.size() == maxHistoryLength) {cleanHistory();}LRUHistory history = new LRUHistory();history.setHash(object.hashCode());history.setTimes(1);histories.add(history);}

访问一存在的资源(visitting):

public void visitting(Object object) {if (histories == null) {throw new NullPointerException();}if (caches == null) {throw new NullPointerException();}int hashCode = object.hashCode();if (inHistory(hashCode)) {boolean offerCache = modifyHistory(hashCode);if (!offerCache) {return;}offerToCache(object);} else if (inCache(object)) {displace(object);} else {throw new NullPointerException("对象不存在");}}

修改历史记录:

boolean offerCache = modifyHistory(hashCode);
if (!offerCache) {return;
}
offerToCache(object);

上面代码的逻辑描述是当我去修改历史记录队列时,发现某一资源可以加入到缓存中去了,就把这个资源添加到缓存中去。

访问缓存队列某元素:

因为LRU-K中的缓存队列就是一个完完全全的LRU,所以LRU-K中缓存队列的访问与LRU中访问方式一致,如下:

private void displace(Object object) {for (Object item : caches) {if (item.equals(object)) {caches.remove(item);break;}}caches.add(object);}

3.Multi Queue(MQ)

解决的问题:

在上面的LRU-K算法中,只要被访问资源的访问次数达到一定数量时,就将这个资源添加到缓存中去。当然,这种做法是合理的,不过,这里我们对这种思路进行了一个扩展。我们按资源被访问的次数对资源进行分级缓存。针对上面的LRU-K算法,我们假设当资源访问次数超过3次时,就加入缓存队列。此时有两个资源A和B,A已经被访问了20次,B已经被访问了4次。这时如果再访问一次B(假设不考虑后续的访问操作),那么B的被淘汰的可能性比A被淘汰的可能要小。可是从整体上来看这种情况,我们知道理论上是A应该比B具备更大的优先级。

上面说到了LRU-K的“缓存污染”(这里并不否认URL-K算法对URL算法的“缓存污染”改善贡献),所以我们就想了一个办法来解决。正是这里要讲解的MQ(Multi Queue)算法。下面请看MQ置换算法的原理图:

图-3 MQ置换算法原理图

算法步骤:

(1)我们需要一个历史记录队列和一个缓存队列的数组。这个数组中保存的就是真正的缓存队列,每个缓存队列和历史队列均按LRU算法进行“淘汰”。而缓存队列与缓存队列之间是按访问次数进行分级;

(2)当我们需要访问一个全新的资源时,就把这个资源加入到最低等级的Q0中,如果有需要淘汰的资源,就把这个淘汰的资源加入到历史队列中;

(3)在某一个缓存队列中资源再次被访问时,就把这个资源加入到队列的头部。如果当前资源被访问的次数达到一定的次数,就把当前资源从当前队列中删除,并加入到更高级的缓存队列的头部;

(4)为了从一定程序上防止(并没有绝对防止)高级别的缓存资源被删除,当一定时间之内,某一资源还未访问,将此资源等级下滑到下一等级;

(5)从上面缓存队列数组中的缓存资源如果被“淘汰”,是被加入到了历史队列。加入历史队列的资源,如果被再次访问可以重新计算资源被访问次数,并添加相应的缓存队列中去;而如果是历史队列中的资源被淘汰了,则是真正意义上的被淘汰。

逻辑实现:

从上面的算法步骤中,我们来编写代码,并展示关键性的代码。如下:

添加新资源:

public void offer(Object object) {if (object == null) {throw new NullPointerException();}CacheBean cacheBean = new CacheBean(object);cacheBean.setTimes(1);cacheBean.setLastVisitTime(System.currentTimeMillis());CacheQueue firstQueue = cacheQueueList.get(0);CacheBean pollObject = firstQueue.offer(cacheBean);if (pollObject == null) {return;}historyQueue.offer(pollObject);}

访问某一资源:

public void visitting(Object object) {if (object == null) {throw new NullPointerException();}CacheBean cacheBean = new CacheBean(object);// 先在缓存队列里找CacheBean tmpBean = null;int currentLevel = 0;boolean needUp = false;for (CacheQueue cacheQueue : cacheQueueList) {if (cacheQueue.contains(cacheBean)) {tmpBean = cacheQueue.get(cacheBean);if (tmpBean.getTimes() < timesDistance * (currentLevel + 1)) {tmpBean.setTimes(tmpBean.getTimes() + 1);cacheQueue.visiting(tmpBean);return;} else {tmpBean.setTimes(tmpBean.getTimes() + 1);cacheQueue.remove(tmpBean);needUp = true;}break;}currentLevel++;}// 是否需要升级if (needUp) {int times = tmpBean.getTimes();times = times > cacheQueueList.size() * timesDistance ? cacheQueueList.size() * timesDistance : times;CacheQueue queue = cacheQueueList.get(times / timesDistance);queue.offer(tmpBean);return;}// 如果数据在history中被重新访问,则重新计算其优先级,移到目标队列的头部if (historyQueue.contains(cacheBean)) {CacheBean reVisitBean = historyQueue.revisiting(cacheBean);reVisitBean.setTimes(reVisitBean.getTimes() + 1);System.out.println(reVisitBean);int times = reVisitBean.getTimes();times = times > cacheQueueList.size() * timesDistance ? cacheQueueList.size() * timesDistance : times;CacheQueue queue = cacheQueueList.get(times / timesDistance);CacheBean cb = queue.offer(reVisitBean);if (cb != null) {historyQueue.offer(cb);}return;}}

Ref:

http://flychao88.iteye.com/blog/1977653

http://www.cs.cmu.edu/~christos/courses/721-resources/p297-o_neil.pdf

http://flychao88.iteye.com/blog/1977642

GitHub源码下载:

https://github.com/William-Hai/LRU-Cache

https://github.com/William-Hai/MultiQueue-Cache

操作系统:基于页面置换算法的缓存原理详解(下)相关推荐

  1. 操作系统:基于页面置换算法的缓存原理详解(上)

    概述: 作为一个学计算机的一定听过缓存(注意这里是缓存,不是缓冲).比如我们在登录网页时,网页就可以缓存一些用户信息:比如我们在写界面代码的时候,可能就会遇到界面的绘制是基于一些缓存算法的.所以,了解 ...

  2. 一文讲懂页面置换算法,带例题详解

    目录 ​什么是页面置换算法? ​缺页中断次数和页面置换次数 ​啥子是缺页? ​啥子是中断? ​啥子是缺页中断? ​缺页中断次数 ​最佳置换算法OPT和先进先出置换算法FIFO ​最佳置换算法OPT ​ ...

  3. 操作系统之页面置换算法(FIFO、LFU、LRU、OPT算法)

    操作系统之页面置换算法(FIFO.LFU.LRU.OPT算法) TIPS: 主存:实际上的物理内存. 虚存(虚拟内存):虚拟存储技术.虚拟内存使计算机系统内存管理的一种技术.它使得应用程序认为它拥有的 ...

  4. 操作系统:页面置换算法(FIFO算法、LRU算法、LFU算法、NRU算法)实验报告

    操作系统实验报告 一.实验名称 :页面置换算法 二.实验目的: 在实验过程中应用操作系统的理论知识. 三.实验内容: 采用C/C++编程模拟实现:FIFO算法.LRU算法.LFU算法.NRU算法四个页 ...

  5. 【操作系统】页面置换算法

    页面置换算法 在进程运行过程中,若需要访问的物理块不在内存中,就需要通过一定的方式来将页面载入内存,而此时内存很可能已无空闲空间,因此就需要一定的算法来选择内存中要被置换的页面,这种算法就被称为页面置 ...

  6. 操作系统:页面置换算法(LRU、FIFO、OPT)

    继续重温操作系统系列知识,页面置换的三种常见算法为:LRU(最近最久未使用).FIFO(先进先出).最佳置换. 部分公司的面试会考到LRU的知识. LRU置换算法 所谓LRU置换算法,单看字面意思较为 ...

  7. 操作系统实验--页面置换算法(OPT/FIFO/LRU/LFU)cpp

    前言 学习笔记 void IntoPage(int m);//将第m条指令转化为对应的页数 int isInside(int number,int Msize);//判断页号是否在内存中 void O ...

  8. fifo页面置换算法java_缓存算法(页面置换算法)-FIFO、LFU、LRU

    转自:http://www.cnblogs.com/dolphin0520/ 1.FIFO算法 FIFO(First in First out),先进先出.其实在操作系统的设计理念中很多地方都利用到了 ...

  9. JPEG算法解密 JPEG原理详解 (转载 by jinchao)

    (转载,个人笔记)https://www.cnblogs.com/Arvin-JIN/p/9133745.html  (随便夸一下CSDN的粘贴功能好强大) 图片压缩有多重要,可能很多人可能并没有一个 ...

最新文章

  1. 拒绝服务(DoS)理解、防御与实现
  2. poj 3579 Median 中间值(二分搜索)
  3. 2017阿里云代码管理服务公测上线
  4. Source Insight中的多行注释
  5. AOP切入同类调用方法不起作用,AopContext.currentProxy()帮你解决这个坑
  6. Koa -- 基于 Node.js 平台的下一代 web 开发框架
  7. layui导出html到pdf,layui打印html页面转成pdf
  8. CVPR 2019 | 腾讯AI:做好活体检测,模型重要,数据亦然
  9. qq动态页面变方格_腾讯QQ音乐9.7.5正式版更新:「歌手主页个人主页」界面全新改版...
  10. 海康威视错误代码0xf_海康威视嵌入式软件工程师笔试题分享(含解答)
  11. Photoshop 入门教程,处理图层「4」如何向多图层图像中添加更多图像?
  12. 书籍-从Paxos到Zookeeper分布式一致性原理与实践
  13. jquery-animate()动画
  14. 智能家居形态逐步演进 机会与挑战并存
  15. IntelliJ IDEA常用快捷键
  16. ah、esp、gre协议_AH,ESP的协议号和它们支持的功能有何差异?两种模式下的差异是什么?...
  17. matplotlib之等高线图
  18. STM32L476入坑-3-新建工程并点亮LED灯
  19. 决策树- 随机森林/GBDT/XGBoost
  20. 深入理解操作系统实验——bomb lab(作弊方法2)

热门文章

  1. buu 篱笆墙的影子
  2. python——动态的增加实例方法、类方法、静态方法
  3. python自动化—web页面操作之窗口切换
  4. [CISCN2018]crackme-java
  5. GetProcAddress 根据 ordinal 导入函数
  6. 【web安全】Spring boot heapdump获取敏感信息
  7. Mysql 查询一天中每半小时记录的数量
  8. POJ 2485 - Highways(求最小生成树的最大权值-Kruskal算法)
  9. 第二章 MCS-51单片机硬件结构与工作原理
  10. collections之defaultdict