|
1 | 1 | #ifndef CONTAINERS_LRU_CACHE_HPP_ |
2 | 2 | #define CONTAINERS_LRU_CACHE_HPP_ |
3 | 3 |
|
4 | | -#include <map> |
| 4 | +#include <unordered_map> |
5 | 5 | #include <list> |
6 | 6 |
|
7 | 7 | #include "errors.hpp" |
8 | 8 |
|
9 | | -template <typename K, typename V> |
| 9 | +template <class K, class V> |
10 | 10 | class lru_cache_t { |
11 | 11 | public: |
12 | | - typedef K key_type; |
13 | | - typedef V value_type; |
14 | | - typedef V &reference; |
15 | | - typedef const V &const_reference; |
16 | | - |
17 | | - typedef std::pair<K, V> entry_pair_t; |
18 | | - typedef std::list<entry_pair_t> cache_list_t; |
19 | | - typedef std::map<K, typename cache_list_t::iterator> cache_map_t; |
20 | | - |
21 | | - typedef typename cache_list_t::iterator iterator; |
22 | | - typedef typename cache_list_t::const_iterator const_iterator; |
23 | | - typedef typename cache_list_t::reverse_iterator reverse_iterator; |
24 | | - typedef typename cache_list_t::const_reverse_iterator const_reverse_iterator; |
25 | | - |
26 | | -private: |
27 | | - // Cache entries, ordered by access time (so cache_list_.begin() points to the |
28 | | - // most recently accessed entry). |
29 | | - cache_list_t cache_list_; |
30 | | - // Map from key values to position in cache list. |
31 | | - cache_map_t cache_map_; |
32 | | - size_t _max; |
33 | | -public: |
34 | | - explicit lru_cache_t(size_t max) : cache_list_(), cache_map_(), _max(max) {} |
35 | | - |
36 | | - // These iterators can be invalidated if someone uses `[]` or `find`; specifically |
37 | | - // it can be the case (particularly with the end positions) that the object you're |
38 | | - // looking at can be recycled by the LRU cache as you insert new entries. |
39 | | - iterator begin() { return cache_list_.begin(); } |
40 | | - const_iterator begin() const { return cache_list_.cbegin(); } |
41 | | - const_iterator cbegin() const { return cache_list_.cbegin(); } |
42 | | - iterator end() { return cache_list_.end(); } |
43 | | - const_iterator end() const { return cache_list_.cend(); } |
44 | | - const_iterator cend() const { return cache_list_.cend(); } |
45 | | - |
46 | | - reverse_iterator rbegin() { return cache_list_.rbegin(); } |
47 | | - const_reverse_iterator rbegin() const { return cache_list_.crbegin(); } |
48 | | - const_reverse_iterator crbegin() const { return cache_list_.crbegin(); } |
49 | | - reverse_iterator rend() { return cache_list_.rend(); } |
50 | | - const_reverse_iterator rend() const { return cache_list_.crend(); } |
51 | | - const_reverse_iterator crend() const { return cache_list_.crend(); } |
52 | | - |
53 | | - size_t size() const { return cache_list_.size(); } |
54 | | - size_t max_size() const { return _max; } |
55 | | - bool empty() const { return cache_list_.empty(); } |
56 | | - |
57 | | - V &operator[](const K &key) { |
58 | | - auto search = cache_map_.find(key); |
59 | | - if (search != cache_map_.end()) { |
60 | | - bump(search); |
61 | | - return search->second->second; |
62 | | - } else { |
63 | | - return insert(K(key)); |
64 | | - } |
| 12 | + explicit lru_cache_t(size_t max_size) : list_(), map_(), max_size_(max_size) { |
| 13 | + guarantee(max_size > 0); |
65 | 14 | } |
66 | | - V &operator[](K &&key) { |
67 | | - auto search = cache_map_.find(key); |
68 | | - if (search != cache_map_.end()) { |
69 | | - bump(search); |
70 | | - return search->second->second; |
| 15 | + |
| 16 | + size_t max_size() const { return max_size_; } |
| 17 | + size_t size() const { return map_.size(); } |
| 18 | + |
| 19 | + // Looks up a key, sets *out to point at its value, and marks the entry 'most |
| 20 | + // recently used'. Returns false if the key is not found. |
| 21 | + bool lookup(const K &key, V **out) { |
| 22 | + auto it = map_.find(key); |
| 23 | + if (it != map_.end()) { |
| 24 | + list_iterator list_iter = it->second; |
| 25 | + list_.splice(list_.end(), list_, list_iter); |
| 26 | + *out = &list_iter->second; |
| 27 | + return true; |
71 | 28 | } else { |
72 | | - return insert(std::move(key)); |
| 29 | + *out = nullptr; |
| 30 | + return false; |
73 | 31 | } |
74 | 32 | } |
75 | | - iterator find(const K &key) { |
76 | | - auto search = cache_map_.find(key); |
77 | | - if (search != cache_map_.end()) { |
78 | | - bump(search); |
79 | | - return search->second; |
| 33 | + |
| 34 | + // Returns true if an insertion was performed. (Does nothing, doesn't even update |
| 35 | + // LRU, if no insertion was performed.) |
| 36 | + bool insert(K key, V value) { |
| 37 | + auto it = map_.find(key); |
| 38 | + if (it != map_.end()) { |
| 39 | + return false; |
80 | 40 | } else { |
81 | | - return cache_list_.end(); |
| 41 | + if (map_.size() == max_size_) { |
| 42 | + list_iterator evictee = list_.begin(); |
| 43 | + DEBUG_VAR size_t count = map_.erase(evictee->first); |
| 44 | + rassert(count == 1); |
| 45 | + list_.pop_front(); |
| 46 | + } |
| 47 | + |
| 48 | + list_.push_back(std::make_pair(key, std::move(value))); |
| 49 | + list_iterator list_iter = list_.end(); |
| 50 | + --list_iter; |
| 51 | + DEBUG_VAR auto res = map_.emplace(std::move(key), list_iter); |
| 52 | + rassert(res.second); |
| 53 | + return true; |
82 | 54 | } |
83 | 55 | } |
| 56 | + |
84 | 57 | private: |
85 | | - V &insert(const K &key) { |
86 | | - cache_list_.push_front(std::make_pair(key, V())); |
87 | | - cache_map_[key] = cache_list_.begin(); |
88 | | - if (cache_list_.size() > _max) { |
89 | | - cache_map_.erase(cache_list_.back().first); |
90 | | - cache_list_.pop_back(); |
91 | | - } |
92 | | - return cache_list_.begin()->second; |
93 | | - } |
94 | | - V &insert(K &&key) { |
95 | | - cache_list_.push_front(std::make_pair(std::move(key), V())); |
96 | | - cache_map_[key] = cache_list_.begin(); |
97 | | - if (cache_list_.size() > _max) { |
98 | | - cache_map_.erase(cache_list_.back().first); |
99 | | - cache_list_.pop_back(); |
100 | | - } |
101 | | - return cache_list_.begin()->second; |
102 | | - } |
103 | | - void bump(const typename cache_map_t::iterator &it) { |
104 | | - entry_pair_t pair = *(it->second); |
105 | | - cache_list_.erase(it->second); |
106 | | - cache_list_.push_front(std::move(pair)); |
107 | | - it->second = cache_list_.begin(); |
108 | | - } |
| 58 | + using list_iterator = typename std::list<std::pair<K, V>>::iterator; |
| 59 | + |
| 60 | + // Elements are pushed onto the back and popped (when evicted) off the front. |
| 61 | + std::list<std::pair<K, V>> list_; |
| 62 | + std::unordered_map<K, list_iterator> map_; |
| 63 | + |
| 64 | + size_t max_size_; |
| 65 | + |
| 66 | + DISABLE_COPYING(lru_cache_t); |
109 | 67 | }; |
110 | 68 |
|
111 | 69 | #endif // CONTAINERS_LRU_CACHE_HPP_ |
|
0 commit comments