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