zoukankan      html  css  js  c++  java
  • 缓存算法之LRU与LFU

    1. LRU算法
    1.1 背景
          目前尽量由于摩尔定律,但是在存储硬件方面始终存在着差异,并且这种差异是不在同一数量级别的区别,例如在容量方面,内存<<外存;而在硬件成本与访问效率方面,内存>>外存。而目前互联网服务平台存在的特点:a. 读多写少,快速ms级响应,因此需要把数据搁在内存上;b. 数据规模巨大,长尾效应,由于数据规模巨大,只能把全量数据搁在外存上。正是由于服务场景需求与存储硬件特征之间的本身矛盾,缓存及相应的淘汰算法由此产生了:

          一个在线服务平台其读取数据的过程:总是优先去离CPU最近的地方内存中读取数据,当有限的内存容量空间读取命中为空被击穿时,则会去外存的数据库或文件系统中读取;而当有限的缓存空间里“人满为患”时,而又有新的热点成员需要加入时,自然需要一定的淘汰机制。本能的基础淘汰算法:最后访问时间最久的成员最先会被淘汰(LRU)。

    1.2 基本原理 

        由于队列具有先进先出的操作特点,所以通常用队列实现LRU,按最后访问的时间先进先出。

       a.  利用队列类型对象,记录最近操作的元素,总是放在队首,这样最久未操作的元素自然被相对移动到队尾;同时,当元素总数达到上限值时,优先移除与淘汰队尾元素。
       b. 利用HashMap辅助对象,快速检索队列中存在的元素。
    1.3 操作
      a. 写入操作: 新建一个元素,把元素插入队列首,当元素总和达到上限值时,同时删除队尾元素。
      b. 读取操作:利用map快速检索,队列相关联的元素。
    1.4 实现源码
      a. 自定义链表方式

      1 // A simple LRU cache written in C++
      2 // Hash map + doubly linked list
      3 #include <iostream>
      4 #include <vector>
      5 #include <ext/hash_map>
      6 using namespace std;
      7 using namespace __gnu_cxx;
      8 
      9 template <class K, class T>
     10 struct Node{
     11   K key;
     12   T data;
     13   Node *prev, *next;
     14 };
     15 
     16 template <class K, class T>
     17 class LRUCache{
     18  public:
     19   LRUCache(size_t size){
     20     entries_ = new Node<K,T>[size];
     21     for(int i=0; i<size; ++i)// 存储可用结点的地址
     22       free_entries_.push_back(entries_+i);
     23     head_ = new Node<K,T>;
     24     tail_ = new Node<K,T>;
     25     head_->prev = NULL;
     26     head_->next = tail_;
     27     tail_->prev = head_;
     28     tail_->next = NULL;
     29   }
     30   ~LRUCache(){
     31     delete head_;
     32     delete tail_;
     33     delete[] entries_;
     34   }
     35   void Put(K key, T data){
     36     Node<K,T> *node = hashmap_[key];
     37     if(node){ // node exists
     38       detach(node);
     39       node->data = data;
     40       attach(node);
     41     }
     42     else
     43     {
     44       if(free_entries_.empty())
     45       {// 可用结点为空,即cache已满
     46         node = tail_->prev;
     47         detach(node);
     48         hashmap_.erase(node->key);
     49       }
     50       else{
     51         node = free_entries_.back();
     52         free_entries_.pop_back();
     53       }
     54       node->key = key;
     55       node->data = data;
     56       hashmap_[key] = node;
     57       attach(node);
     58     }
     59   }
     60   
     61   T Get(K key){
     62     Node<K,T> *node = hashmap_[key];
     63     if(node){
     64       detach(node);
     65       attach(node);
     66       return node->data;
     67     }
     68     else{// 如果cache中没有,返回T的默认值。与hashmap行为一致
     69       return T();
     70     }
     71   }
     72  private:
     73   // 分离结点
     74   void detach(Node<K,T>* node){
     75     node->prev->next = node->next;
     76     node->next->prev = node->prev;
     77   }
     78   // 将结点插入头部
     79   void attach(Node<K,T>* node){
     80     node->prev = head_;
     81     node->next = head_->next;
     82     head_->next = node;
     83     node->next->prev = node;
     84   }
     85  private:
     86   hash_map<K, Node<K,T>* > hashmap_;
     87   vector<Node<K,T>* > free_entries_; // 存储可用结点的地址
     88   Node<K,T> *head_, *tail_;
     89   Node<K,T> *entries_; // 双向链表中的结点
     90 };
     91 
     92 int main(){
     93   hash_map<int, int> map;
     94   map[9]= 999;
     95   cout<<map[9]<<endl;
     96   cout<<map[10]<<endl;
     97   LRUCache<int, string> lru_cache(100);
     98   lru_cache.Put(1, "one");
     99   cout<<lru_cache.Get(1)<<endl;
    100   if(lru_cache.Get(2) == "")
    101     lru_cache.Put(2, "two");
    102   cout<<lru_cache.Get(2);
    103   return 0;
    104 }

      b. 采用stl::list类型实现方式

      1 #include <iostream>
      2 #include <list>
      3 #include <ext/hash_map>
      4 #include <stdint.h>
      5 #include <string>
      6 
      7 template<class T1, class T2>
      8 class Lru
      9 {
     10  public:
     11   typedef std::list<std::pair<T1, T2> > List;
     12   typedef typename List::iterator iterator;
     13   typedef __gnu_cxx::hash_map<T1, iterator> Map;
     14 
     15   Lru()
     16   {
     17     size_ = 1000;
     18     resize(size_);
     19   }
     20 
     21   ~Lru()
     22   {
     23     clear();
     24   }
     25 
     26   void resize(int32_t size)
     27   {
     28     if (size > 0)
     29     {
     30       size_ = size;
     31     }
     32   }
     33 
     34   T2* find(const T1& first)
     35   {
     36     typename Map::iterator i = index_.find(first);
     37 
     38     if (i == index_.end())
     39     {
     40       return NULL;
     41     }
     42     else
     43     {
     44       typename List::iterator n = i->second;
     45       list_.splice(list_.begin(), list_, n);
     46       return &(list_.front().second);
     47     }
     48   }
     49 
     50   void remove(const T1& first)
     51   {
     52     typename Map::iterator i = index_.find(first);
     53     if (i != index_.end())
     54     {
     55       typename List::iterator n = i->second;
     56       list_.erase(n);
     57       index_.erase(i);
     58     }
     59   }
     60 
     61   void insert(const T1& first, const T2& second)
     62   {
     63     typename Map::iterator i = index_.find(first);
     64     if (i != index_.end())
     65     { // found
     66       typename List::iterator n = i->second;
     67       list_.splice(list_.begin(), list_, n);
     68       index_.erase(n->first);
     69       n->first = first;
     70       n->second = second;
     71       index_[first] = n;
     72     }
     73     else if (size() >= size_ )
     74     { // erase the last element
     75       typename List::iterator n = list_.end();
     76       --n; // the last element
     77       list_.splice(list_.begin(), list_, n);
     78       index_.erase(n->first);
     79       n->first = first;
     80       n->second = second;
     81       index_[first] = n;
     82     }
     83     else
     84     {
     85       list_.push_front(std::make_pair(first, second));
     86       typename List::iterator n = list_.begin();
     87       index_[first] = n;
     88     }
     89   }
     90 
     91   /// Random access to items
     92   iterator begin()
     93   {
     94     return list_.begin();
     95   }
     96 
     97   iterator end()
     98   {
     99     return list_.end();
    100   }
    101 
    102   int size()
    103   {
    104     return index_.size();
    105   }
    106 
    107   // Clear cache
    108   void clear()
    109   {
    110     index_.clear();
    111     list_.clear();
    112   }
    113 
    114  private:
    115   int32_t size_;
    116   List list_;
    117   Map index_;
    118 };
    119 
    120 
    121 
    122 int main(void)
    123 {
    124   std::cout << "hello world " << std::endl;
    125   tfs::Lru<uint32_t, std::string> name_cache;
    126   name_cache.insert(1, "one");
    127   name_cache.insert(2, "two");
    128 
    129   std::string* value = name_cache.find(1);
    130   const char* v = value->c_str();
    131   std::cout << "result of the key 1 is: " << *name_cache.find(1) << std::endl;
    132       
    133   return 0;
    134 }

    2. LRFU缓存算法 

    2.1 前言
          缓存算法有许多种,各种缓存算法的核心区别在于它们的淘汰机制。通常的淘汰机制:所有元素按某种量化的重要程度进行排序,分值最低者则会被优先淘汰。而重要程度的量化指标又通常可以参考了两个维度:最后被访问的时间和最近被访问的频率次数。依次例如如下:

        1. LRU(Least Recently Used ),总是淘汰最后访问时间最久的元素。

           这种算法存在着问题:可能由于一次冷数据的批量查询而误淘汰大量热点的数据。

        2. LFU(Least Frequently Used ),总是淘汰最近访问频率最小的元素。

           这种算法也存在明显的问题:  a. 如果频率时间度量是1小时,则平均一天每个小时内的访问频率1000的热点数据可能会被2个小时的一段时间内的访问频率是1001的数据剔除掉;b. 最近新加入的数据总会易于被剔除掉,由于其起始的频率值低。本质上其“重要性”指标访问频率是指在多长的时间维度进行衡量?其难以标定,所以在业界很少单一直接使用。也由于两种算法的各自特点及缺点,所以通常在生产线上会根据业务场景联合LRU与LFU一起使用,称之为LRFU。

    2.2 实现机制
           正是由于LRU与LFU算法各具特点,所以在行业生产线上通常会联合两者使用。我们可以在LRU的实现基础上稍作衍生,能实现LRFU缓存机制,即可以采用队列分级的思想,例如oracle利用两个队列维护访问的数据元素,按被访问的频率的维度把元素分别搁在热端与冷端队列;而在同一个队列内,最后访问时间越久的元素会越被排在队列尾。

       
    参考
      1.   https://en.wikipedia.org/wiki/Least_frequently_used
      2.   http://code.taobao.org/p/tfs/src/
      3.   http://www.hawstein.com/posts/lru-cache-impl.html

  • 相关阅读:
    Ubuntu 安装 NTP 服务
    Packer 如何将 JSON 的配置升级为 HCL2
    WinRM 如何设置 TrustedHosts
    Windows 10 如何设置网络属性为私有
    Windows 使用 PowerShell 来管理另外一台 Windows 机器
    Windows PowerShell ISE 是什么和 PowerShell 有什么区别
    Spring事务传播属性和隔离级别
    @SpringBootApplication(exclude={DataSourceAutoConfiguration.class})注解作用
    杂文 | 如何在演讲中讲个好故事
    2.2 思考框架:什么样的代码才是高效的代码
  • 原文地址:https://www.cnblogs.com/gisorange/p/4947868.html
Copyright © 2011-2022 走看看