3.13.3. LRU Cache
The LRU caching scheme is to evict the least recently used item when the cache is full. Since it is a cache or a key value store, the basic data structure should be a map. To maintain the insertion order, we need to use a list to store the ordered items.
🐾 When getting an item from LRU cache, the item will be refreshed.
1struct lru_cache {
2 lru_cache(int capacity) : capa_(capacity) {}
3 int get(int key) {
4 if (map_.count(key)) { // It key exists, update it.
5 const auto& value = map_[key]->second;
6 promote(key, value);
7 return value;
8 }
9 return -1;
10 }
11 void put(int key, int value) {
12 // If cache is full while inserting, remove the last one.
13 if (map_.count(key) == 0 and list_.size() == capa_) {
14 auto del = list_.back();
15 list_.pop_back();
16 map_.erase(del.first);
17 }
18 promote(key, value);
19 }
20
21 private:
22 list<pair<int, int>> list_; // key, value
23 unordered_map<int, LITER> map_; // key, list iterator
24 int capa_;
25 // Promote (key, iterator of (key, value) pair)
26 void promote(int key, int value) {
27 if (map_.count(key)) {
28 list_.erase(map_[key]);
29 }
30 list_.emplace_front(key, value);
31 map_[key] = list_.begin();
32 }
3.13.3.1. LRU-Customized Eviction Scheme
1struct node {
2 int k;
3 int v;
4 int z; // vol, or size
5};
6
7class lru_by_size {
8 list<node> l; // key is unique in the ll
9 unordered_map<int, list<node>::iterator> m;
10 const int CAPSIZE;
11
12 protected:
13 int cz = 0; // current size
14 bool __promote(list<node> &l, list<node>::iterator it) {
15 if (l.begin() != it) {
16 l.splice(l.begin(), l, it, next(it)); // a.splice(x, b, [, ))
17 return true;
18 }
19 return false;
20 }
21
22 void __remove_node(list<node>::iterator it) {
23 m.erase(it->k);
24 l.erase(it);
25 cz -= it->z;
26 }
27
28 // nn - new node we just add to the list
29 void __evict(const node &nn) {
30 while (CAPSIZE < cz) {
31 // eviction policy
32 // traverse list backward and find the node with size equal to nn.z and
33 // distance(or time) less than T with tail (concept of time window), if we
34 // couldn't find it, evict the last one policy 1
35 auto it = l.end();
36 int count = 0, T = 10, evicted = 0;
37 while (it != l.begin() and count < T) {
38 it--;
39 count++;
40 if (it->z == nn.z and it->k != nn.k) {
41 __remove_node(it);
42 evicted = 1;
43 break;
44 }
45 }
46 // policy 2 - remove from the last one until the capacity is enough
47 if (!evicted) {
48 while (CAPSIZE < cz and !l.empty()) {
49 __remove_node(prev(l.end()));
50 }
51 }
52 }
53 }
54
55 public:
56 lru_by_size(int cap_) : CAPSIZE(cap_) {}
57
58 int get(int k) {
59 if (!m.count(k)) return INT_MIN;
60 int r = m[k]->v;
61 if (__promote(l, m[k])) m[k] = l.begin();
62 return r;
63 }
64
65 // first we don't consider constraint of size, we just blindly
66 // push_front/promote then we do eviction
67 bool put(int k, int v, int z) {
68 // 0. edge case
69 if (z > CAPSIZE) return false;
70 node nn = {k, v, z};
71 // 1. old node
72 if (m.count(k)) {
73 if (__promote(l, m[k])) m[k] = l.begin();
74 if (l.front().z != z) {
75 cz += z - l.front().z;
76 l.front().z = z;
77 }
78 if (l.front().v != v) l.front().v = v;
79 } else {
80 // 2. new code
81 // 2.1 evict ?
82 // 2.2 add new node to front
83 l.push_front(nn);
84 m[k] = l.begin();
85 cz += z;
86 }
87 __evict(nn);
88 return true;
89 }
90
91 int getsize() { return cz; }
92};
How to promote an item in a list to the front:
[cling]$ list<int> l={2,4,1,3,6,5}
(std::list<int> &) { 2, 4, 1, 3, 6, 5 }
[cling]$ auto it = next(next(l.begin()));
[cling]$ l.splice(l.begin(), l, it, next(it))
[cling]$ l
(std::list<int> &) { 1, 2, 4, 3, 6, 5 }