LRU和LFU 算法(页面置换算法)
LRU和LFU的区别
LRU和LFU都是内存管理的页面置换算法。
LRU:最近最少使用(最长时间)淘汰算法(Least Recently Used)。LRU是淘汰最长时间没有被使用的页面。
LFU:最不经常使用(最少次)淘汰算法(Least Frequently Used)。LFU是淘汰一段时间内,使用次数最少的页面。
例子
假设LFU方法的时期T为10分钟,访问如下页面所花的时间正好为10分钟,内存块大小为3。若所需页面顺序依次如下:
2 1 2 1 2 3 4
---------------------------------------->
- 当需要使用页面4时,内存块中存储着1、2、3,内存块中没有页面4,就会发生缺页中断,而且此时内存块已满,需要进行页面置换。
- 若按LRU算法,应替换掉页面1。因为页面1是最长时间没有被使用的了,页面2和3都在它后面被使用过。
- 若按LFU算法,应换页面3。因为在这段时间内,页面1被访问了2次,页面2被访问了3次,而页面3只被访问了1次,一段时间内被访问的次数最少。
LRU 关键是看页面最后一次被使用到发生替换的时间长短,时间越长,页面就会被置换;
LFU关键是看一定时间段内页面被使用的频率(次数),使用频率越低,页面就会被置换。
LRU算法适合:较大的文件比如游戏客户端(最近加载的地图文件);
LFU算法适合:较小的文件和零碎的文件比如系统文件、应用程序文件 ;
LRU消耗CPU资源较少,LFU消耗CPU资源较多。
LRU (最长时间)
最近最久未使用算法, LRU是淘汰最长时间没有被使用的页面
功能
- 缓存容量capacity为正整数,缓存的key、value均为int类型
- 读缓存
func get(key int) int
:- key已存在,返回对应value
- key不存在,返回-1
- 写缓存func put(key int, value int):
- key已存在,修改对应value
- key不存在,写入该组缓存,若写入前缓存容量已达上限,则应淘汰最久未使用的缓存(强调:读、写缓存均视为使用)
数据结构
使用双向链表维护缓存的上一次使用时间:
- 约定:链表正方向(从头部到尾部)节点按照使用时间排序——越早使用(即久未使用)的节点,越靠近链表尾部
- 维护:每使用一次缓存,就将该缓存对应的链表节点移动到链表头部;缓存淘汰时,只需要删除尾部节点即可
增加一个map,记录
key
到链表节点的映射关系; 解决如果只使用双向链表,每次判断key
是否存在时,都要遍历链表
- cache:
map[int]*listNode
,key
到节点的映射; 其中 listNode data:key
,value
- list:
*listNode
,双向链表,维护缓存的上一次使用时间 - capacity:
int
,链表容量
伪代码
- 读缓存
- key存在:
- 在原链表中删除该缓存节点,重新插入到链表头部,
- 返回对应的value
- key不存在:
- 返回-1
- key存在:
- 写缓存(更新缓存)
- Key存在:
- 更新缓存节点的value值
- 在原链表中删除该缓存节点,并把该重新插入到链表头部
- Key不存在:
- 容量已达上限:
- 在链表中删除尾部节点(记录该节点的key)
- 根据上一步中记录的key,删除对应的映射关系
- 根据输入参数构造新的节点:
- 将新的节点插入链表头部
- 新增key到新的节点的映射关系
- 容量未达上限:
- 根据输入参数构造新的节点:
- 将新的节点插入链表头部
- 新增key到新的节点的映射关系
- 容量已达上限:
- Key存在:
Golang代码实现
// 双向链表节点
type doublyListNode struct {key intvalue intprev *doublyListNodenext *doublyListNode
}// 构造一个双向空链表(首尾几点都是空节点)
func newDoublyList() *doublyListNode {headNode := &doublyListNode{}tailNode := &doublyListNode{}headNode.next = tailNodetailNode.prev = headNodereturn headNode
}// 把节点添加到链表头部
func (dl *doublyListNode) addToHead(node *doublyListNode) {dl.next.prev = nodenode.next = dl.nextdl.next = nodenode.prev = dl
}// 删除链表中的节点
func removeNode(node *doublyListNode) {node.next.prev = node.prevnode.prev.next = node.next
}// LRUCache 具体的缓存
type LRUCache struct {cache map[int]*doublyListNodehead *doublyListNodetail *doublyListNodecapacity int
}// Constructor 构建缓存容器
func Constructor(capacity int) LRUCache {dl := newDoublyList()return LRUCache{cache: make(map[int]*doublyListNode),head: dl,tail: dl.next,capacity: capacity,}
}func (lruCache *LRUCache) Get(key int) int {// 根据key 获取缓存v, ok := lruCache.cache[key]// 如果没有缓存, 返回-1if !ok {return -1}// 如果有缓存removeNode(v) // 移除该缓存lruCache.head.addToHead(v) // 把缓存添加双向链表头部return v.value
}// Put 新建缓存
func (lruCache *LRUCache) Put(key int, value int) {// 已经有缓存if v, ok := lruCache.cache[key]; ok { // v 是双链表中的节点v.value = value // 更新链表节点中的值lruCache.cache[key] = v // 更新缓存中映射关系removeNode(v) // 移除该缓存lruCache.head.addToHead(v) // 把缓存添加双向链表头部return}// 缓存超长 淘汰缓存if len(lruCache.cache) >= lruCache.capacity {node := lruCache.tail.prevremoveNode(node) // 删除该节点delete(lruCache.cache, node.key) // 清除 最近最少使用的缓存}newNode := &doublyListNode{key: key,value: value,}lruCache.cache[key] = newNodelruCache.head.addToHead(newNode)
}
LFU (最少次)
功能
- 缓存容量capacity、缓存的key和value均为自然数(可以为0,代码中单独处理)
- 读缓存func get(key int) int:(与lru相同)
- key已存在,返回对应value
- key不存在,返回-1
- 写缓存func put(key int, value int):
- key已存在,修改对应value
- key不存在,写入该组缓存,若写入前缓存容量已达上限,则应淘汰使用次数最少的缓存(记其使用次数为n);
- 若使用次数为n的缓存数大于一个,则淘汰最久未使用的缓存(即,此时遵守lru规则)
数据结构
// LFUCache 具体的缓存 frequency 是使用次数
type LFUCache struct {recent map[int]*doublyListNode // frequency 到使用次数为 frequency 的节点中,最近使用的一个的映射count map[int]int // frequency 到对应频率的节点数量的映射cache map[int]*doublyListNode // key到节点的映射list *doublyList // 双向链表,维护缓存的使用次数(优先)和上一次使用时间capacity int // 容量
}
伪代码
- 读缓存
- 存在:(记节点frequency为n)
- 若存在其他frequency = n+1的节点,则将节点移动到所有frequency = n+1的节点的前面;
- 否则,若存在其他frequency = n的节点,且当前节点不是最近节点,则将节点移动到所有frequency = n的节点的前面;
- 否则,不移动节点(该情况下,节点就应该呆在它现在的位置)
- 更新recent
- 更新count
- 将节点frequency +1
- 返回节点的value
- 不存在:返回-1
- 存在:(记节点frequency为n)
- 写缓存
- key存在
- 参考读缓存——key存在,额外修改对应的value即可
- 不存在:
- 若当前缓存容量已达上限:
- 淘汰尾部的缓存节点(记节点freq为n)
- 若不存在其他freq = n的节点,则将recent置空
- 更新cache
- 更新count
- 构造新节点:key,value,frequency = 1
- 是否存在其他frequency = 1的节点:
- 存在:插入到它们的前面
- 不存在:插入链表尾部
- 更新recent
- 更新cache
- 更新count
- 若当前缓存容量已达上限:
- key存在
Golang代码实现
// 双向链表
type doublyList struct {head *doublyListNodetail *doublyListNode
}// 删除尾结点
func (dl *doublyList) removeTail() {pre := dl.tail.prev.prevpre.next = dl.taildl.tail.prev = pre
}// 链表是否为空
func (dl *doublyList) isEmpty() bool {return dl.head.next == dl.tail
}// 双向链表节点
type doublyListNode struct {key intvalue intfrequency int // 使用次数prev *doublyListNodenext *doublyListNode
}// 在某一个节点之前插入一个节点
func addBefore(currNode *doublyListNode, newNode *doublyListNode) {pre := currNode.prevpre.next = newNodenewNode.next = currNodecurrNode.prev = newNodenewNode.prev = pre
}// LFUCache 具体的缓存
type LFUCache struct {recent map[int]*doublyListNode // frequency 到使用次数为 frequency 的节点中,最近使用的一个的映射count map[int]int // frequency 到对应频率的节点数量的映射cache map[int]*doublyListNode // key到节点的映射list *doublyList // 双向链表,维护缓存的使用次数(优先)和上一次使用时间capacity int // 容量
}func removeNode(node *doublyListNode) {node.prev.next = node.nextnode.next.prev = node.prev
}// Constructor 构建缓存容器
func Constructor(capacity int) LFUCache {return LFUCache{recent: make(map[int]*doublyListNode),count: make(map[int]int),cache: make(map[int]*doublyListNode),list: newDoublyList(),capacity: capacity,}
}func newDoublyList() *doublyList {headNode := &doublyListNode{}tailNode := &doublyListNode{}headNode.next = tailNodetailNode.prev = headNodereturn &doublyList{head: headNode,tail: tailNode,}
}func (lfu *LFUCache) Get(key int) int {if lfu.capacity == 0 {return -1}node, ok := lfu.cache[key]if !ok { // key不存在return -1}// key已存在next := node.nextif lfu.count[node.frequency+1] > 0 {// 存在其他使用次数为n+1的缓存,将指定缓存移动到所有使用次数为n+1的节点之前removeNode(node)addBefore(lfu.recent[node.frequency+1], node)} else if lfu.count[node.frequency] > 1 && lfu.recent[node.frequency] != node {// 不存在其他使用次数为n+1的缓存,但存在其他使用次数为n的缓存,且当前节点不是最近的节点// 将指定缓存移动到所有使用次数为n的节点之前removeNode(node)addBefore(lfu.recent[node.frequency], node)}// 更新recentlfu.recent[node.frequency+1] = nodeif lfu.count[node.frequency] <= 1 { // 不存在其他freq = n的节点,recent置空lfu.recent[node.frequency] = nil} else if lfu.recent[node.frequency] == node { // 存在其他freq = n的节点,且recent = node,将recent向后移动一位lfu.recent[node.frequency] = next}// 更新使用次数对应的节点数lfu.count[node.frequency+1]++lfu.count[node.frequency]--// 更新缓存使用次数node.frequency++return node.value
}// Put 新建缓存
func (lfu *LFUCache) Put(key int, value int) {if lfu.capacity == 0 {return}node, ok := lfu.cache[key]if ok { // key已存在lfu.Get(key)node.value = valuereturn}// key不存在if len(lfu.cache) >= lfu.capacity { // 缓存已满,删除最后一个节点,相应更新cache、count、recent(条件)tailNode := lfu.list.tail.prevlfu.list.removeTail()if lfu.count[tailNode.frequency] <= 1 {lfu.recent[tailNode.frequency] = nil}lfu.count[tailNode.frequency]--delete(lfu.cache, tailNode.key)}newNode := &doublyListNode{key: key,value: value,frequency: 1,}// 插入新的缓存节点if lfu.count[1] > 0 {addBefore(lfu.recent[1], newNode)} else {addBefore(lfu.list.tail, newNode)}// 更新recent、count、cachelfu.recent[1] = newNodelfu.count[1]++lfu.cache[key] = newNode
}
作者微信:foolish_is_me
作者邮箱:big_ox@163.com
LRU和LFU 算法(页面置换算法)相关推荐
- 计算机操作系统——页面置换算法
声明:本篇博客参考书籍<计算机操作系统>(西安电子科技大学出版社) 文章目录 一.最佳页面置换算法 1.基本知识 2.算法思想 二.先进先出(FIFO)页面置换算法 1.基本知识 2.算法 ...
- 缺页中断与页面置换算法
目录 缺页中断 页面置换算法: LRU算法 缺页中断 缺页:如果进程被调度,该进程需要使用的外存页(数据)不存在于数据块中,这个现象就叫做缺页.如果这个数据此时不在,就会将这个数据从加入到数据块首 ...
- 操作系统实验:页面置换算法的模拟实现及命中率对比(学习笔记)
操作系统实验:页面置换算法的模拟实现及命中率对比(学习笔记) 题目要求 输入要求 输出要求 编程平台 实验成果 开始模拟 错误输入 退出程序 代码实现 抽象数据类型定义 指令地址流生成 指令地址流到页 ...
- 最近最久未使用页面置换算法
在一个请求分页系统中,采用最近最久未使用页面置换算法时,假如一个作业的页面走向为4.3.2.1.4.3.5.4.3.2.1.5,当分配给该作业的物理块数M分别为3和4时,试计算在访问过程中所发生的缺页 ...
- java简单巡回置换算法程序代码_巡回置换算法(巡回置换算法实现流程)
LRU是Least Recently Used的缩写,即最近最少使用页面置换算法,是为虚拟页式存储管理服务的.LRU算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的. 在一个请求分页系统中, ...
- 页面置换算法 - FIFO、LFU、LRU
缓存算法(页面置换算法)-FIFO. LFU. LRU 在前一篇文章中通过leetcode的一道题目了解了LRU算法的具体设计思路,下面继续来探讨一下另外两种常见的Cache算法:FIFO. LFU ...
- 操作系统之页面置换算法(FIFO、LFU、LRU、OPT算法)
操作系统之页面置换算法(FIFO.LFU.LRU.OPT算法) TIPS: 主存:实际上的物理内存. 虚存(虚拟内存):虚拟存储技术.虚拟内存使计算机系统内存管理的一种技术.它使得应用程序认为它拥有的 ...
- 操作系统:页面置换算法(FIFO算法、LRU算法、LFU算法、NRU算法)实验报告
操作系统实验报告 一.实验名称 :页面置换算法 二.实验目的: 在实验过程中应用操作系统的理论知识. 三.实验内容: 采用C/C++编程模拟实现:FIFO算法.LRU算法.LFU算法.NRU算法四个页 ...
- 页面置换算法——C/C++实现 [ OTP, FIFO, LRU, LFU + 开源代码 + 详细解析]
⌛️ 文章目录 零.运行结果图 一.最佳置换算法(OPT) 二.先进先出算法(FIFO) 三.最近最久未使用算法(LRU) 四.最不经常使用算法(LFU) 五.完整代码 -- C语言版本 六.完整代码 ...
最新文章
- 论MySQL何时使用索引,何时不使用索引
- SQL Cookbook:一、检索记录(13)按模式搜索
- Java类加载机制的理解
- mysql审计 社区版有吗_mysql 5.6 社区版上审计功能,不扯皮
- 织梦编辑器加HTML视频显示很小,织梦去掉编辑器自动加div的方法即大小字情况...
- 【李宏毅机器学习】Basic Concept 基础概念(p4) 学习笔记
- Linux系统内核正式进入5.0版本时代
- 10分钟部署一套开源表单系统
- 字符集本地化(locale)与输入法系列讲座-----(1) UTF-8 and Unicode FAQ
- PDF转换CAD有什么方法
- Vue学习(学习打卡Day14)
- 想学建模该从何开始?
- Python基础教程 | 第三章 字符串
- 台式计算机箱ip5x,IP5X防水是个什么概念?
- RestTemplate 发送请求 清除Cookie
- 啊哈C——学习2.6一起来找茬
- STM32F103代码远程升级(五)基于MQTT协议WiFi远程升级代码的实现
- 潘震seo教程:总结seo毒害案例
- java浮点数取余数
- 几种网站压力测试工具调研与使用
热门文章
- ThinkPHP 5.0.22 5.1.29 GetShell Exploit
- Mongodb ObjectId格式
- php如何开启bc math,BC Math
- 美国海军计算机工作站,美国海军用上了3D打印,工控机智能支持3D打印技术
- golang go语言_Go(Golang)编程语言
- ROS(indigo)_pr2_simulator仿真(gazebo)示例
- 创业做亚马逊测评到底可以吗?有市场吗?靠谱吗?
- 暴走英雄坛服务器维护时间,暴走英雄坛1.4.1版本维护公告 两段前期支线剧情更新...
- c#图像相似度比较demo
- 三星急了,在中端机市场上下重本,但并不足够