1. 程式人生 > 其它 >快取演算法之LRU與LFU

快取演算法之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