LRU Cache

本文介绍了LRU页面置换算法及其在缓存管理中的应用,讨论了预读失效问题和如何通过双链表机制以及访问次数限制来避免这个问题,同时提出了缓存污染的解决方案。
摘要由CSDN通过智能技术生成

LRU 介绍

LRULeast Recently Used,最进最少使用)页面置换算法的思想是:在缺页中断发生时,置换未使用时间最长的页面

该思想依赖局部性原理:在前面几条指令中频繁使用的页面很可能在后面的几条指令中被使用

为了实现 LRU,需要在内存中维护一个所有页面的链表,最近使用的页面在表头,最近没用到的在表尾。然后再用一个哈希表维护链表各节点位置信息,方便后续移动节点。

LRU Cache

题目描述

题目链接:设计 LRU 缓存结构

set 的时候需要判断缓存中有没有,如果有的话,将相应的值更新,然后移到链表头部即可;如果没有的话,要先判断是否已满,如果满了要先将链表尾部节点删除,然后再添加新节点。

get 逻辑比较简单,没有的话返回 -1,有的话返回相应值,并将节点移到链表头部。

package main

import "container/list"

type Solution struct {
	table map[int]*list.Element
	list  *list.List
	cap   int
}

type data struct {
	key int
	val int
}

func Constructor(capacity int) Solution {
	return Solution{
		table: make(map[int]*list.Element),
		list:  list.New(),
		cap:   capacity,
	}
}

func (this *Solution) get(key int) int {
	if value, ok := this.table[key]; ok {
		this.list.MoveToFront(value)
		res := value.Value.(data)
		return res.val
	} else {
		return -1
	}
}

func (this *Solution) set(key int, value int) {
	if val, ok := this.table[key]; ok {
		val.Value = data{key, value}
		this.list.MoveToFront(val)
	} else {
		if len(this.table) == this.cap {
			del := this.list.Back()
			this.list.Remove(del)
			val2 := del.Value.(data).key
			delete(this.table, val2)
		}

		this.list.PushFront(data{key, value})
		this.table[key] = this.list.Front()
	}
}

LRU 的问题

下方内容参考:如何避免预读失效造成的影响

预读失效

在 Linux 上加载数据时,并不是使用多少就加载多少。根据局部性原理,会多读一些数据放入 PageCache 中。

如果这些被提前加载进来的页,并没有被访问,相当于这个预读工作是白做了,这个就是预读失效

如果使用传统的 LRU 算法,就会把「预读页」放到 LRU 链表头部,当内存空间不够的时候,还需要把末尾的页淘汰掉。

如果这些「预读页」如果一直不会被访问到,就会出现一个很奇怪的问题,不会被访问的预读页却占用了 LRU 链表前排的位置,而末尾淘汰的页,可能是热点数据,这样就大大降低了缓存命中率

如何避免预读失效?

最好就是让预读页停留在内存里的时间要尽可能的短,让真正被访问的页才移动到 LRU 链表的头部,从而保证真正被读取的热数据留在内存里的时间尽可能长

Linux 是如何避免预读失效的?

Linux 操作系统实现两个了 LRU 链表:活跃 LRU 链表(active_list)和非活跃 LRU 链表(inactive_list)

  • active list 活跃内存页链表,这里存放的是最近被访问过(活跃)的内存页
  • inactive list 不活跃内存页链表,这里存放的是很少被访问(非活跃)的内存页

LRU double list

有了这两个链表后,预读页就只需要加入到 inactive list 区域的头部,当页被真正访问的时候,才将页插入 active list 的头部。如果预读的页一直没有被访问,就会从 inactive list 移除,这样就不会影响 active list 中的热点数据。

缓存污染

如果「只要数据被访问一次,就将数据加入到活跃 LRU 链表头部」,还存在缓存污染的问题

当我们在批量读取数据的时候,由于数据被访问了一次,这些数据都会被加入到「活跃 LRU 链表」里,之前缓存在活跃 LRU 链表里的热点数据全部都被淘汰了,如果这些数据在很长一段时间都不会被访问的话,那么整个活跃 LRU 链表就被污染了

如何避免缓存污染?

只要数据被访问一次,就将数据加入活跃 LRU 链表,这种 LRU 算法进入活跃 LRU 链表的门槛太低了。因为门槛太低,导致在发生缓存污染的时候,很容就将原本在活跃 LRU 链表里的热点数据淘汰了。

所以,需要我们提高进入到活跃 LRU 链表的门槛,这样就能有效地保证活跃 LRU 链表里的热点数据不会被轻易替换掉。

我们可以在访问次数上再做一些限制,比如当访问三次后才将相应的页放到活跃 LRU 链表。

在批量读取数据时候,如果这些大量数据只会被访问一次,那么它们就不会进入到活跃 LRU 链表,也就不会把热点数据淘汰,只会待在非活跃 LRU 链表中,后续很快也会被淘汰。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值