常用缓存淘汰算法对比

上篇文章介绍了最常用的LRU算法及实现,本篇总结常用缓存淘汰算法,归总对比。

一、LFU

(Least Frequently Used):最近最低使用频次被淘汰

实现:通过count记录缓存数据的使用次数,数据块按照引用计数排序,计数相同则按照时间排序。

1. 新加入数据插入到队列尾部(因为引用计数为1);

2. 队列中的数据被访问后,引用计数增加,队列重新排序;

3. 当需要淘汰数据时,将已经排序的列表最后的数据块删除。

 public int removeCache() {  
        Iterator<CacheObject<K, V>> iterator = cacheMap.values().iterator();  
        int count  = 0 ;  
        long minAccessCount = Long.MAX_VALUE  ;  
        while(iterator.hasNext()){  
            CacheObject<K, V> cacheObject = iterator.next();  
              
            if(cacheObject.isExpired() ){  
                iterator.remove();   
                count++ ;  
                continue ;  
            }else{  
                minAccessCount  = Math.min(cacheObject.accessCount , minAccessCount)  ;  
            }  
        }  
          
        if(count > 0 ) return count ;  
          
        if(minAccessCount != Long.MAX_VALUE ){  
              
            iterator = cacheMap.values().iterator();  
              
            while(iterator.hasNext()){  
                CacheObject<K, V> cacheObject = iterator.next();  
                  
                cacheObject.accessCount  -=  minAccessCount ;  
                  
                if(cacheObject.accessCount <= 0 ){  
                    iterator.remove();  
                    count++ ;  
                }  
                  
            }  
              
        }  
          
        return count;  
    }  

缺点:

1、需要维护一个队列记录所有数据的访问记录,每个数据都需要维护引用计数。

2、需要记录所有数据的访问记录,内存消耗较高

2、需要基于引用计数重排序,性能消耗较高。

优点:

1、按照频率排序,LFU命中效率要优于LRU。

2、能避免因非热点数据介入导致的缓存命中率下降的问题。(先经过一次频率计算了)

LFU算法变种详情访问:LFU

二、LRU-K

相比LRU,LRU-K需要多维护一个队列,用于记录所有缓存数据被访问的历史。只有当数据的访问次数达到K次的时候,才将数据放入缓存。当需要淘汰数据时,LRU-K会淘汰第K次访问时间距当前时间最大的数据。

实现:优先级队列,算法复杂度和代价比较高

1. 数据第一次被访问,加入到访问历史列表;

2. 如果数据在访问历史列表里后没有达到K次访问,则按照一定规则(FIFO,LRU)淘汰;

3. 当访问历史队列中的数据访问次数达到K次后,将数据索引从历史队列删除,将数据移到缓存队列中,并缓存此数据,缓存队列重新按照时间排序;

4. 缓存数据队列中被再次访问后,重新排序;

5. 需要淘汰数据时,淘汰缓存队列中排在末尾的数据,即:淘汰“倒数第K次访问离现在最久”的数据。

优点:LRU-K具有LRU的优点(热点高频数据命中率高,命中率比LRU要高),同时能降低“缓存污染”问题

缺点:

1、适应性差,需要大量的数据访问才能将历史访问记录清除掉。

2、历史记录提高内存消耗:由于LRU-K还需要记录那些被访问过、但还没有放入缓存的对象,因此内存消耗会比LRU要多;当数据量很大的时候,内存消耗会比较可观。

3、时间排序拉高CPU消耗:LRU-K需要基于时间进行排序(可以需要淘汰时再排序,也可以即时排序),CPU消耗比LRU要高。

实际应用中LRU-2是综合各种因素后最优的选择,LRU-3或者更大的K值命中率会高

三、FIFO

基本队列,不多说

四、Two queues

Two queues算法类似于LRU-2,不同点在于2Q将LRU-2中的访问历史队列(注意这不是缓存数据的)改为一个FIFO缓存队列,即2Q算法=一个是FIFO队列,一个是LRU队列。命中率高于LRU

实现:当数据第一次访问时,2Q算法将数据缓存在FIFO队列里面,当数据第二次被访问时,则将数据从FIFO队列移到LRU队列里,两个队列各自按照自己的方法淘汰数据。

1. 新访问的数据插入到FIFO队列;

2. 如果数据在FIFO队列中一直没有被再次访问,则最终按照FIFO规则淘汰;

3. 如果数据在FIFO队列中被再次访问,则将数据移到LRU队列头部;

4. 如果数据在LRU队列再次被访问,则将数据移到LRU队列头部;

5. LRU队列淘汰末尾的数据。

五、Multi Queue

优先缓存访问次数多的数据,根据访问频率将数据划分为多个队列,不同的队列具有不同的访问优先级

实现:

1. 新插入的数据放入Q0;

2. 每个队列按照LRU管理数据;

3. 当数据的访问次数达到一定次数,需要提升优先级时,将数据从当前队列删除,加入到高一级队列的头部;

4. 为了防止高优先级数据永远不被淘汰,当数据在指定的时间里访问没有被访问时,需要降低优先级,将数据从当前队列删除,加入到低一级的队列头部;

5. 需要淘汰数据时,从最低一级队列开始按照LRU淘汰;每个队列淘汰数据时,将数据从缓存中删除,将数据索引加入Q-history头部;

6. 如果数据在Q-history中被重新访问,则重新计算其优先级,移到目标队列的头部;

7. Q-history按照LRU淘汰数据的索引。

对比点

对比

命中率

LRU-2 > MQ(2) > 2Q > LRU

复杂度

LRU-2 > MQ(2) > 2Q > LRU

代价

LRU-2  > MQ(2) > 2Q > LRU

实际应用中需要根据业务的需求和对数据的访问情况进行选择,并不是命中率越高越好。例如:虽然LRU看起来命中率会低一些,且存在”缓存污染“的问题,但由于其简单和代价小,实际应用中反而应用更多。

总结自:LRU算法 缓存淘汰策略

猜你喜欢

转载自blog.csdn.net/Daybreak1209/article/details/82790104