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 }

3.13.3.2. LFU