2022-11-19 17:19阅读: 488评论: 0推荐: 0

页面置换算法:LRU和LFU

页面置换算法简介

在地址映射过程中,若在页面中发现所要访问的页面不在内存中,则产生缺页中断。当发生缺页中断时,如果操作系统内存中没有空闲页面,则操作系统必须在内存选择一个页面将其移出内存,以便为即将调入的页面让出空间。而用来选择淘汰哪一页的规则叫做页面置换算法。

页面置换算法的好坏,将直接影响系统的性能,常见的页面置换算法:

  • 最佳置换算法(OPT)
  • 最近未使用页面置换算法(NRU):
  • 先进先出置换算法(FIFO)
  • 最近最久未使用算法(LRU)
  • 最少使用置换算法(LFU)

一个好的页面置换算法,应做到减少页面置换的频率,尽量将以后不会用到的或较长时间不会使用的页面给置换出。

下面,我们主要介绍一下应用比较广泛的页面置换算法:LRU 和 LFU 算法。

LRU和LFU算法

它们的区别如下:

  • LRU:最近最少使用(最长时间)淘汰算法(Least Recently Used),LRU会淘汰最长时间没有被使用的页面。
  • LFU:最不经常使用(最少次)淘汰算法(Least Frequently Used),LFU会淘汰一段时间内使用次数最少的页面。

它们的应用场景:

  • LRU:消耗CPU资源较少,适合较大的文件比如游戏客户端(最近加载的地图文件);
  • LFU:消耗CPU资源较多,适合较小的文件和零碎的文件比如系统文件、应用程序文件 。

算法实现

我们分别通过两道力扣上的算法题,来介绍 LRU 和 LFU 算法。

LRU算法

题目:Leetcode.16.25

面试题 16.25. LRU 缓存

思路

利用Hash表和双向链表维护所有的节点数据,Hash表能在O(1)的时间内查找数据,双向链表能在O(1)时间内进行数据插入和删除。

代码实现

【Java实现】

class LRUCache {
private Map<Integer, Node> map;
private DeList cache;
private int capacity;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<>();
cache = new DeList();
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
// 如果值存在,需要将其提升为最近使用的元素
makeRecently(key);
return map.get(key).val;
}
public void put(int key, int value) {
if (map.containsKey(key)) {
// 删除旧值
deleteKey(key);
// 在链表尾部添加一个新元素
addRecently(key, value);
return ;
}
// 判断是否需要移除头部的元素
if (capacity == cache.size()) {
removeLeastRecently();
}
// 在链表尾部添加一个新元素
addRecently(key, value);
}
// 将某个key提升为最近使用的
private void makeRecently(int key) {
Node node = map.get(key);
cache.remove(node);
cache.addLast(node);
}
// 添加最近使用的元素
private void addRecently(int key, int val) {
Node node = new Node(key, val);
cache.addLast(node);
map.put(key, node);
}
// 删除一个key
private Node deleteKey(int key) {
Node node = map.get(key);
map.remove(node);
cache.remove(node);
return node;
}
// 删除最久未使用的key
private void removeLeastRecently() {
Node node = cache.removeFirst();
map.remove(node.key);
}
// 双向链表的节点
class Node {
public int key, val;
public Node next, prev;
public Node (int key, int val) {
this.key = key;
this.val = val;
}
}
// 双向链表
class DeList {
private Node head, tail;
private int size;
public DeList() {
head = new Node(0, 0);
tail = new Node(0, 0);
head.next = tail;
tail.prev = head;
size = 0;
}
// 在链表尾部添加一个节点
public void addLast(Node node) {
node.prev = tail.prev;
node.next = tail;
tail.prev.next = node;
tail.prev = node;
size++;
}
// 移除一个节点
public Node remove(Node node) {
node.prev.next = node.next;
node.next.prev = node.prev;
size--;
return node;
}
// 移除链表头部的节点
public Node removeFirst() {
if (head.next == tail) {
return null;
}
return remove(head.next);
}
public int size() {
return size;
}
}
}

【Python实现】

class DLinkedNode(object):
def __init__(self, key: int = 0, value: int = 0):
self.key = key
self.value = value
self.next = None
self.prev = None
class LRUCache:
def __init__(self, capacity: int):
self.cache = dict()
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
@staticmethod
def _remove_node(node: DLinkedNode):
""" 双向链表删除一个节点 """
node.next.prev = node.prev
node.prev.next = node.next
return node
def _add_to_head(self, node: DLinkedNode):
""" 将一个节点插入到双向链表的头部 """
node.next = self.head.next
node.prev = self.head
self.head.next.prev = node
self.head.next = node
return
def _remove_tail(self) -> DLinkedNode:
""" 删除双向链表尾部的元素 """
return self._remove_node(self.tail.prev)
def _move_to_head(self, node: DLinkedNode):
""" 将一个任意节点移动到双向链表头部 """
self._remove_node(node)
self._add_to_head(node)
def get(self, key: int) -> int:
if key not in self.cache:
return -1
node = self.cache.get(key)
self._move_to_head(node)
return node.value
def put(self, key: int, value: int) -> None:
if key not in self.cache:
new_node = DLinkedNode(key, value)
self.cache.update({key: new_node})
self._add_to_head(new_node)
self.size += 1
if self.size > self.capacity:
old_node = self._remove_tail()
self.cache.pop(old_node.key)
self.size -= 1
else:
node = self.cache.get(key)
node.value = value
self._move_to_head(node)

LFU算法

LFU算法相当于淘汰 访问频率最低 的数据。访问频率,在代码实现的时候,可以将其转换为 访问次数

题目:Leetcode.460

460. LFU 缓存

思路

为了高效地访问每个 key 对应的 value ,很容易想到使用 hash 表记录每一个键值对,同时,使用用一个 hash 表记录每一个 key 的记录访问频率。

为了高效地淘汰频率低的 key ,我们还需要将每一个访问频率对应的 key 值记录下来,需要注意的是,如果多个 key 的访问频率相等时,优先淘汰最早的 key ,所以,我们需要使用有序集合存储 key 的顺序。

流程

用到的数据结构如下:

  • 使用 hash 表 keyToValue 记录所有 keyvalue 键值对,保证存储和读取的时间复杂度都是O(1)
  • 使用 hash 表 keyToFreq 记录每一个 key 的访问频率(次数);
  • 使用 hash 表 freqToKeys 记录每一个访问频率与该频率对应的 key 值列表。

查询get(key)

查询的逻辑比较简单,如果 key 不在keyToValue 中,就返回 1,否则,就增加key的访问次数加1,并返回对应的value。

存储put(key, value)

如果 key 不在 keyToValue 中,就将其保存到 keyToValue 中,并将访问次数加 1,否则,就判断 keyToValue 是否超过缓存的最大容量,超过就要删除最少访问次数的 key 释放一个空间,用于保存新的 key

代码实现

【Python实现】

from collections import defaultdict
class LFUCache(object):
def __init__(self, capacity: int):
self._key_to_value = dict()
self._key_to_freq = dict()
self._freq_to_keys = defaultdict(list)
self._min_freq = 0
self._capacity = capacity
def increase_freq(self, key: int):
freq = self._key_to_freq.get(key)
# 更新使用频率
self._key_to_freq.update({key: freq + 1})
self._freq_to_keys[freq].remove(key)
# 新的频率中增加这个key
self._freq_to_keys[freq + 1].append(key)
if len(self._freq_to_keys[freq]) == 0:
self._freq_to_keys.pop(freq)
if freq == self._min_freq:
self._min_freq += 1
def remove_min_freq_key(self):
keys = self._freq_to_keys[self._min_freq]
# 淘汰最先被插入的key
deleted_key = keys.pop(0)
if len(keys) == 0:
self._freq_to_keys.pop(self._min_freq)
# 更新key-value表
self._key_to_value.pop(deleted_key)
self._key_to_freq.pop(deleted_key)
def get(self, key: int) -> int:
if key not in self._key_to_value:
return -1
self.increase_freq(key)
return self._key_to_value.get(key)
def put(self, key: int, value: int):
if self._capacity <= 0:
return
if key in self._key_to_value:
self._key_to_value.update({key: value})
self.increase_freq(key)
return
if self._capacity <= len(self._key_to_value):
self.remove_min_freq_key()
self._key_to_value.update({key: value})
self._key_to_freq.update({key: 1})
self._freq_to_keys[1].append(key)
self._min_freq = 1

为了简化数据结构,将上述代码中的 valuefreq 封装为一个 Node 对象,这样操作起来更简单、更通用一些,也支持其他的数据类型。

优化后的代码

from collections import defaultdict
class Node(object):
def __init__(self, value: int = None, freq: int = 1):
self.value = value # 记录节点的值
self.freq = freq # 记录节点访问的次数
def increase_frequency(self):
self.freq += 1
def get_frequency(self):
return self.freq
def __str__(self):
return "%s" % (self.value)
class LFUCache(object):
def __init__(self, capacity: int):
# 记录键值对
self._key_to_value = dict()
# 记录每一个频率对应的key值集合
self._freq_to_keys = defaultdict(list)
# 记录缓存中最小访问频率
self._min_freq = 0
# 缓存大小
self._capacity = capacity
def increase_freq(self, key: int):
# 获取当前key的访问次数
freq = self._key_to_value.get(key).get_frequency()
# 将其访问次数加1
self._key_to_value.get(key).increase_frequency()
# 从旧的访问频率中删除这个key
self._freq_to_keys[freq].remove(key)
# 新的频率中增加这个key
self._freq_to_keys[freq + 1].append(key)
# 如果旧的频率里面没有key了,及时清除这个频率
if len(self._freq_to_keys[freq]) == 0:
self._freq_to_keys.pop(freq)
# 如果删除的频率是最小的,还需要将最小频率加1
if freq == self._min_freq:
self._min_freq += 1
def remove_min_freq_key(self):
# 获取最小频率对应的key值集合
keys = self._freq_to_keys[self._min_freq]
# 淘汰最先被插入的key
deleted_key = keys.pop(0)
# 如果没有其他key值了,就将该频率删除
if len(keys) == 0:
self._freq_to_keys.pop(self._min_freq)
# 更新key-value表
self._key_to_value.pop(deleted_key)
def get(self, key: int) -> int:
# 如果key没有,就返回-1
if key not in self._key_to_value:
return -1
# 增加该key的访问次数
self.increase_freq(key)
# 返回对应的值
return self._key_to_value.get(key)
def put(self, key: int, value: int):
# 如果缓存为负数则退出
if self._capacity <= 0:
return
# 如果key已经存在
if key in self._key_to_value:
# 更新key的值
self._key_to_value.update({key: value})
# 并增加该key的访问次数
self.increase_freq(key)
return
# key是第一次加入,且缓存已经满了,就需要删除一个最少访问次数的key
if self._capacity <= len(self._key_to_value):
self.remove_min_freq_key()
# 保存新加入的key-value对,并将最小访问次数置为1
self._key_to_value.update({key: Node(value)})
self._freq_to_keys[1].append(key)
self._min_freq = 1
if __name__ == "__main__":
lfu = LFUCache(2)
print(lfu.put(1, 1))
print(lfu.put(2, 2))
print(lfu.get(1))
print(lfu.put(3, 3))
print(lfu.get(2))
print(lfu.get(3))
print(lfu.put(4, 4))
print(lfu.get(1))
print(lfu.get(3))
print(lfu.get(4))

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/16906492.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(488)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.