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

    一、LRU算法

    1.1 背景

      目前尽量由于摩尔定律,但是在存储硬件方面始终存在着差异,并且这种差异是不在同一数量级别的区别,例如在容量方面,内存<<外存;而在硬件成本与访问效率方面,内存>>外存。而目前互联网服务平台存在的特点:

      a. 读多写少,快速ms级响应,因此需要把数据搁在内存上;

      b. 数据规模巨大,长尾效应,由于数据规模巨大,只能把全量数据搁在外存上。正是由于服务场景需求与存储硬件特征之间的本身矛盾,缓存及相应的淘汰算法由此产生了:

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

    1.2 基本原理 

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

        a.  利用队列类型对象,记录最近操作的元素,总是放在队首,这样最久未操作的元素自然被相对移动到队尾;同时,当元素总数达到上限值时,优先移除与淘汰队尾元素。

        b. 利用HashMap辅助对象,快速检索队列中存在的元素。

    1.3 操作

      a. 写入操作: 新建一个元素,把元素插入队列首,当元素总和达到上限值时,同时删除队尾元素。
      b. 读取操作:利用map快速检索,队列相关联的元素。

    1.4 实现源码

    1.4.1 自定义链表方式

      1 #pragma once
      2 #ifndef LRU_h_
      3 #define LRU_h_
      4 
      5 #include <unordered_map>
      6 #include <vector>
      7 
      8 using namespace std;
      9 
     10 struct Node {
     11     int val;
     12     int key;
     13     Node* next;
     14     Node* prev;
     15     Node(int v = 0, int k = 0) : val(v), key(k), next(nullptr), prev(nullptr) {}
     16 };
     17 class Solution {
     18 public:
     19     /**
     20      * lru design
     21      * @param operators int整型vector<vector<>> the ops
     22      * @param k int整型 the k
     23      * @return int整型vector
     24      */
     25     vector<int> LRU(vector<vector<int> >& operators, int k) {
     26         // write code here
     27         cap = k;
     28         cursize = 0;
     29         int len = operators.size();
     30         head = nullptr;
     31         tail = nullptr;
     32 
     33         vector<int> ans;
     34         for (int i = 0; i < len; i++)
     35         {
     36             int opt = operators[i][0];
     37             int key = operators[i][1];
     38             if (opt == 1)
     39             {
     40                 int value = operators[i][2];
     41                 if (mp.count(key))
     42                 {
     43                     Node* node = mp[key];
     44                     node->val = value;
     45                     Move_to_head(node);
     46                 }
     47                 else
     48                 {
     49                     Node* node = new Node(value, key);
     50                     add_to_head(node);
     51                     mp[key] = node;
     52                     cursize++;
     53                     while (cursize > cap)
     54                     {
     55                         Node* temp = tail;
     56                         remove_tail();
     57                         mp.erase(temp->key);
     58                         delete temp;
     59                         cursize--;
     60                     }
     61                 }
     62             }
     63             else
     64             {
     65                 if (mp.count(key))
     66                 {
     67                     Node* node = mp[key];
     68                     ans.push_back(node->val);
     69                     Move_to_head(node);
     70                 }
     71                 else
     72                 {
     73                     ans.push_back(-1);
     74                 }
     75             }
     76         }
     77         return ans;
     78     }
     79 
     80     void remove_tail()
     81     {
     82         if (tail == head)
     83         {
     84             tail = nullptr;
     85             head = nullptr;
     86         }
     87         else
     88         {
     89             tail = tail->prev;
     90             tail->next = nullptr;
     91         }
     92     }
     93 
     94     void add_to_head(Node* node)
     95     {
     96         if (head == nullptr)
     97         {
     98             head = tail = node;
     99         }
    100         else
    101         {
    102             node->next = head;
    103             node->prev = nullptr;
    104             head->prev = node;
    105             head = node;
    106         }
    107     }
    108 
    109     void Move_to_head(Node* node)
    110     {
    111         if (node == head)
    112         {
    113             return;
    114         }
    115         else if (node == tail)
    116         {
    117             remove_tail();
    118             add_to_head(node);
    119         }
    120         else
    121         {
    122             node->prev->next = node->next;
    123             node->next->prev = node->prev;
    124             add_to_head(node);
    125         }
    126     }
    127 
    128 
    129 private:
    130     Node* head;
    131     Node* tail;
    132     std::unordered_map<int, Node*> mp;
    133     int cap;
    134     int cursize;
    135 };
    136 
    137 #endif // !LRU_h_

    1.4.2 采用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 int main(void)
    121 {
    122   std::cout << "hello world " << std::endl;
    123   tfs::Lru<uint32_t, std::string> name_cache;
    124   name_cache.insert(1, "one");
    125   name_cache.insert(2, "two");
    126 
    127   std::string* value = name_cache.find(1);
    128   const char* v = value->c_str();
    129   std::cout << "result of the key 1 is: " << *name_cache.find(1) << std::endl;
    130 
    131   return 0;
    132 }

    二、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利用两个队列维护访问的数据元素,按被访问的频率的维度把元素分别搁在热端与冷端队列;而在同一个队列内,最后访问时间越久的元素会越被排在队列尾。

    2.3 实现

      最重要的是数据节点需要重载<运算符,从而指定节点在set容器中的规则:先按频率大小进行升序排列,频率相同则按照访问时间先后进行排序。

     1 #pragma once
     2 #ifndef LFU_H_
     3 #define LFU_H_
     4 
     5 #include <unordered_map>
     6 #include <set>
     7 
     8 struct LFUnode
     9 {
    10     int cnt;
    11     int time;
    12     int key;
    13     int value;
    14     LFUnode(int _cnt = 0, int _time = 0, int _key = 0, int _value) : cnt(_cnt), time(_time), key(_key), value(_value) {}
    15 
    16     bool operator<(const LFUnode item) const
    17     {
    18         return cnt == item.cnt ? time < item.time : cnt < item.cnt;
    19     }
    20 };
    21 
    22 
    23 class LFU 
    24 {
    25 private:
    26     int capacity;
    27     int time;
    28     std::unordered_map<int, LFUnode> keymp;
    29     std::set<LFUnode> cntmp;
    30 public:
    31     LFU(int _capacity) : capacity(_capacity), time(0)
    32     {
    33         keymp.clear();
    34         cntmp.clear();
    35     }
    36 
    37     int get(int key)
    38     {
    39         if (capacity == 0)
    40             return -1;
    41         auto item = keymp.find(key);
    42         if (item == keymp.end())
    43             return -1;
    44         LFUnode node = item->second;
    45         cntmp.erase(node);
    46         node.cnt++;
    47         node.time = ++time;
    48         cntmp.insert(node);
    49         item->second = ndoe;
    50         return node.value;
    51     }
    52 
    53     bool put(int key, int value)
    54     {
    55         if (capacity == 0)
    56             return false;
    57         auto item = keymp.find(key);
    58         if (item == keymp.end())
    59         {
    60             if (keymp.size() >= capacity)
    61             {
    62                 keymp.erase(cntmp.begin()->key);
    63                 cntmp.erase(cntmp.begin());
    64             }
    65             LFUnode node = LFUnode(1, ++time, key, value);
    66             keymp.insert({ key, node });
    67             cntmp.insert(node);
    68         }
    69         else
    70         {
    71             LFUnode node = item->second;
    72             cntmp.erase(node);
    73             node.value = value;
    74             node.time = ++time;
    75             node.cnt++;
    76             cntmp.insert(node);
    77             item->second = node;
    78         }
    79         return true;
    80     }
    81 };
    82 
    83 #endif // !

    三、参考文章

    https://www.cnblogs.com/gisorange/p/4947868.html

    本文来自博客园,作者:Mr-xxx,转载请注明原文链接:https://www.cnblogs.com/MrLiuZF/p/15186639.html

  • 相关阅读:
    [机器学习] k-近邻算法(knn)
    [博客] 博客园侧边栏公告设置访问人数及访客国家来源
    Ubuntu搭建hugo博客
    CodeForces
    Javaweb开发入门___1
    JDBC的学习
    Mysql的学习7___权限和数据库设计
    Mysql的学习6____事物,索引,备份,视图,触发器
    Mysql的学习5___Mysql常用函数,聚合函数,sql编程
    Mysql的学习3___数据的管理,主键 外键 以及增改删
  • 原文地址:https://www.cnblogs.com/MrLiuZF/p/15186639.html
Copyright © 2011-2022 走看看