代码:ARC
Wiki:Adaptive replacement cache
论文:A low overhead high performance buffer replacement algorithm
ARC算法是为了整个LFU和LRU两种替换算法的优点,实现他们之间的自适应的workload调整。算法把有效缓存队列划分为两个,T1和T2,其中T1是LRU队列用于保存最近使用的条目、T2是LFU队列用于保存最常使用的条目。 他们各自都包含了一个名为ghost list的淘汰队列,分别命名为B1、B2,但tracking操作(即外部添加、查找操作)只针对T1、T2进行.ARC算法会根据缓存命中情况自动调整T1、T2的大小,以保证整个缓存既定长度的恒定。当新元素较多的时候,T1长度会增长T2长度会缩小,而当旧元素命中较多时候 T2长度会增长T1长度会缩小。
理论ARC算法核心结构:
- . . . [ B1 <-[ T1 <-!-> T2 ]-> B2 ] . .
- [ . . . . [ . . . . . . ! . .^. . . . ] . . . . ]
- [ fixed cache size (c) ]
- [ L ]
- L可视为一个有效的缓存队列整体,其长度恒定不变。通过T1、T2长度的变化改变偏重P的大小。
-
-
- 首次添加 Add("A")
- | T1(LRU) | | B1(LRU) | | T2 (LFU)| | B2 (LFU)|
- |----------| |----------| |---------| |---------|
- ele(A)-> | ele(A) | | | | | | |
- |----------| |----------| |---------| |---------|
-
- 第二次添加或者访问 Add("A") / Get("A")
- | T1(LRU) | | B1(LRU) | | T2 (LFU)| | B2 (LFU)|
- |----------| |----------| |---------| |---------|
- | | | | | ele(A) | | |
- |----------| |----------| |---------| |---------|
-
- 对于已被T1或者T2淘汰的数据,再次添加 Add("A"),会直接进入T2
- | T1(LRU) | | B1(LRU) | | T2 (LFU)| | B2 (LFU)|
- |----------| |----------| |---------| |---------|
- | | | ele(A) | | | | |
- |----------| |----------| |---------| |---------|
-
- | T1(LRU) | | B1(LRU) | | T2 (LFU)| | B2 (LFU)|
- |----------| |----------| |---------| |---------|
- | | | | | ele(A) | | |
- |----------| |----------| |---------| |---------|
-
新元素: + 若空间不足,淘汰T2 + 添加新元素到T1
已存在元素: + 若在B1或B2存在,移动到T2 + 若空间不足,淘汰T1
查询命中 + 若T1查询命中,移动到T2
本包跟2Q算法实现类似,将T2用LRU队列来实现。其结构如下图所示。T1,T2,B1,B2依然按照理论ARC中的设计,区别只是T2 B2用的是LRU算法而不是LFU。
本包算法实现结构:
- | T1(LRU) | | B1(LRU) | | T2 (LRU)| | B2 (LRU)|
- |----------| |----------| |---------| |---------|
- ele(A)-> | ele(A) | | | | | | |
- |----------| |----------| |---------| |---------|
-
ARC算法结构对象:
- type ARCCache struct {
- size int // 整体缓存的既定长度
- p int // P T1和T2的侧重值
-
- t1 simplelru.LRUCache // T1 最近使用队列
- b1 simplelru.LRUCache // B1 T1淘汰队列
-
- t2 simplelru.LRUCache // T2 最常使用队列
- b2 simplelru.LRUCache // B2 T2淘汰队列
-
- lock sync.RWMutex // 读写锁
- }
-
- type ARCCache interface {
- NewARC(size int) (*TwoQueueCache, error) // 创建一个ARC算法的缓存对象
- Add(key, value interface{}) // 添加元素,若元素不存在则添加元素,若元素存在则更新元素
- Get(key interface{}) (value interface{}, ok bool) // 获取元素
- Contains(key interface{}) (ok bool) // 查看该元素是否存在,但不会更新元素位置
- Peek(key interface{}) (value interface{}, ok bool) // 获取元素,但不会更新元素位置
- Remove(key interface{}) bool // 删除元素
- Len() int // 返回队列中元素数量
- Purge() // 清除队列中全部元素
- }
-
ARC算法核心过程是Add(),在Add()方法中需要针对不同情况处理四个队列的淘汰问题和T1、T2长度问题,最后更新偏重值P。
Add() 添加元素
- func (c *ARCCache) Add(key, value interface{}) {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- if c.t1.Contains(key) { // 若在T1存在,移动到T2
- c.t1.Remove(key)
- c.t2.Add(key, value)
- return
- }
-
- if c.t2.Contains(key) { // 若在T2存在,更新值
- c.t2.Add(key, value)
- return
- }
-
- if c.b1.Contains(key) { // 若在B1存在,移动到T2
- delta := 1 // 侧重增量
- b1Len := c.b1.Len()
- b2Len := c.b2.Len()
- if b2Len > b1Len {
- delta = b2Len / b1Len
- }
- if c.p+delta >= c.size { // 调整侧重,靠近T1这边
- c.p = c.size
- } else {
- c.p += delta
- }
-
- if c.t1.Len()+c.t2.Len() >= c.size {
- c.replace(false) // 淘汰T2,缓存窗口往T1移动
- }
-
- c.b1.Remove(key)
-
- c.t2.Add(key, value)
- return
- }
-
- if c.b2.Contains(key) { // 若在B2存在,移动到T2
- delta := 1 // 侧重增量
- b1Len := c.b1.Len()
- b2Len := c.b2.Len()
- if b1Len > b2Len {
- delta = b1Len / b2Len
- }
- if delta >= c.p { // 调整侧重,靠近T2这边
- c.p = 0
- } else {
- c.p -= delta
- }
-
- if c.t1.Len()+c.t2.Len() >= c.size {
- c.replace(true) // 淘汰T1,缓存窗口往T2移动
- }
-
- c.b2.Remove(key)
-
- c.t2.Add(key, value)
- return
- }
-
- // T1 T2 B1 B2 都无该元素,添加到T1
- if c.t1.Len()+c.t2.Len() >= c.size { // 超出整体缓存既定大小
- c.replace(false) // T2淘汰
- }
-
- if c.b1.Len() > c.size-c.p {
- c.b1.RemoveOldest()
- }
- if c.b2.Len() > c.p {
- c.b2.RemoveOldest()
- }
-
- c.t1.Add(key, value)
- return
- }
-
Get方法与2Q相似,T1命中的则移动到T2队列
Get() 获取元素
- func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) {
- c.lock.Lock()
- defer c.lock.Unlock()
-
- if val, ok := c.t1.Peek(key); ok { // 若在T1存在,移动到T2
- c.t1.Remove(key)
- c.t2.Add(key, val)
- return val, ok
- }
-
- if val, ok := c.t2.Get(key); ok {
- return val, ok
- }
-
- // No hit
- return nil, false
- }
-