Line data Source code
1 : /*
2 : * LRUCache11 - a templated C++11 based LRU cache class that allows
3 : * specification of
4 : * key, value and optionally the map container type (defaults to
5 : * std::unordered_map)
6 : * By using the std::map and a linked list of keys it allows O(1) insert, delete
7 : * and
8 : * refresh operations.
9 : *
10 : * This is a header-only library and all you need is the LRUCache11.hpp file
11 : *
12 : * Github: https://github.com/mohaps/lrucache11
13 : *
14 : * This is a follow-up to the LRUCache project -
15 : * https://github.com/mohaps/lrucache
16 : *
17 : * Copyright (c) 2012-22 SAURAV MOHAPATRA <mohaps@gmail.com>
18 : *
19 : * Permission to use, copy, modify, and distribute this software for any
20 : * purpose with or without fee is hereby granted, provided that the above
21 : * copyright notice and this permission notice appear in all copies.
22 : *
23 : * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
24 : * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
25 : * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
26 : * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
27 : * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
28 : * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
29 : * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
30 : */
31 :
32 : /*! @cond Doxygen_Suppress */
33 :
34 : #pragma once
35 : #include <algorithm>
36 : #include <cstdint>
37 : #include <list>
38 : #include <mutex>
39 : #include <stdexcept>
40 : #include <unordered_map>
41 :
42 : namespace lru11
43 : {
44 : /*
45 : * a noop lockable concept that can be used in place of std::mutex
46 : */
47 : class NullLock
48 : {
49 : public:
50 6330646 : void lock()
51 : {
52 6330646 : }
53 :
54 6323746 : void unlock()
55 : {
56 6323746 : }
57 :
58 : bool try_lock()
59 : {
60 : return true;
61 : }
62 : };
63 :
64 : #if defined(__clang__)
65 : #pragma clang diagnostic push
66 : #pragma clang diagnostic ignored "-Wweak-vtables"
67 : #endif
68 :
69 : /**
70 : * error raised when a key not in cache is passed to get()
71 : */
72 : class KeyNotFound : public std::invalid_argument
73 : {
74 : public:
75 1 : KeyNotFound() : std::invalid_argument("key_not_found")
76 : {
77 1 : }
78 : };
79 :
80 : #if defined(__clang__)
81 : #pragma clang diagnostic pop
82 : #endif
83 :
84 : template <typename K, typename V> struct KeyValuePair
85 : {
86 : public:
87 : K key;
88 : V value;
89 :
90 6909 : KeyValuePair(const K &k, const V &v) : key(k), value(v)
91 : {
92 6909 : }
93 :
94 26329 : KeyValuePair(const K &k, V &&v) : key(k), value(std::move(v))
95 : {
96 26329 : }
97 :
98 : private:
99 : KeyValuePair(const KeyValuePair &) = delete;
100 : KeyValuePair &operator=(const KeyValuePair &) = delete;
101 : };
102 :
103 : /**
104 : * The LRU Cache class templated by
105 : * Key - key type
106 : * Value - value type
107 : * MapType - an associative container like std::unordered_map
108 : * LockType - a lock type derived from the Lock class (default:
109 : *NullLock = no synchronization)
110 : *
111 : * The default NullLock based template is not thread-safe, however passing
112 : *Lock=std::mutex will make it
113 : * thread-safe
114 : */
115 : template <class Key, class Value, class Lock = NullLock,
116 : class Map = std::unordered_map<
117 : Key, typename std::list<KeyValuePair<Key, Value>>::iterator>>
118 : class Cache final
119 : {
120 : public:
121 : typedef KeyValuePair<Key, Value> node_type;
122 : typedef std::list<KeyValuePair<Key, Value>> list_type;
123 : typedef Map map_type;
124 : typedef Lock lock_type;
125 : using Guard = std::lock_guard<lock_type>;
126 :
127 : /**
128 : * the max size is the hard limit of keys and (maxSize + elasticity) is the
129 : * soft limit
130 : * the cache is allowed to grow till maxSize + elasticity and is pruned back
131 : * to maxSize keys
132 : * set maxSize = 0 for an unbounded cache (but in that case, you're better
133 : * off using a std::unordered_map directly anyway! :)
134 : */
135 110947 : explicit Cache(size_t maxSize = 64, size_t elasticity = 10)
136 110947 : : maxSize_(maxSize), elasticity_(elasticity)
137 : {
138 110947 : }
139 :
140 92054 : ~Cache() = default;
141 :
142 839 : size_t size() const
143 : {
144 1678 : Guard g(lock_);
145 839 : return cache_.size();
146 : }
147 :
148 5744 : bool empty() const
149 : {
150 11488 : Guard g(lock_);
151 5744 : return cache_.empty();
152 : }
153 :
154 72770 : void clear()
155 : {
156 145540 : Guard g(lock_);
157 72770 : cache_.clear();
158 72770 : keys_.clear();
159 72770 : }
160 :
161 8576 : void insert(const Key &k, const Value &v)
162 : {
163 8576 : Guard g(lock_);
164 8576 : const auto iter = cache_.find(k);
165 8576 : if (iter != cache_.end())
166 : {
167 1667 : iter->second->value = v;
168 1667 : keys_.splice(keys_.begin(), keys_, iter->second);
169 1667 : return;
170 : }
171 :
172 6909 : keys_.emplace_front(k, v);
173 6909 : cache_[k] = keys_.begin();
174 6909 : prune();
175 : }
176 :
177 26901 : Value &insert(const Key &k, Value &&v)
178 : {
179 53802 : Guard g(lock_);
180 26901 : const auto iter = cache_.find(k);
181 26901 : if (iter != cache_.end())
182 : {
183 572 : iter->second->value = std::move(v);
184 572 : keys_.splice(keys_.begin(), keys_, iter->second);
185 572 : return keys_.front().value;
186 : }
187 :
188 26329 : keys_.emplace_front(k, std::move(v));
189 26329 : cache_[k] = keys_.begin();
190 26329 : prune();
191 26329 : return keys_.front().value;
192 : }
193 :
194 5976881 : bool tryGet(const Key &kIn, Value &vOut)
195 : {
196 11953766 : Guard g(lock_);
197 5976881 : const auto iter = cache_.find(kIn);
198 5976881 : if (iter == cache_.end())
199 : {
200 4480669 : return false;
201 : }
202 1496211 : keys_.splice(keys_.begin(), keys_, iter->second);
203 1496211 : vOut = iter->second->value;
204 1496211 : return true;
205 : }
206 :
207 : /**
208 : * The const reference returned here is only
209 : * guaranteed to be valid till the next insert/delete
210 : */
211 4 : const Value &get(const Key &k)
212 : {
213 5 : Guard g(lock_);
214 4 : const auto iter = cache_.find(k);
215 4 : if (iter == cache_.end())
216 : {
217 1 : throw KeyNotFound();
218 : }
219 3 : keys_.splice(keys_.begin(), keys_, iter->second);
220 6 : return iter->second->value;
221 : }
222 :
223 208113 : Value *getPtr(const Key &k)
224 : {
225 416226 : Guard g(lock_);
226 208113 : const auto iter = cache_.find(k);
227 208113 : if (iter == cache_.end())
228 : {
229 16472 : return nullptr;
230 : }
231 191641 : keys_.splice(keys_.begin(), keys_, iter->second);
232 191641 : return &(iter->second->value);
233 : }
234 :
235 : /**
236 : * returns a copy of the stored object (if found)
237 : */
238 1 : Value getCopy(const Key &k)
239 : {
240 1 : return get(k);
241 : }
242 :
243 4098 : bool remove(const Key &k)
244 : {
245 8196 : Guard g(lock_);
246 4098 : auto iter = cache_.find(k);
247 4098 : if (iter == cache_.end())
248 : {
249 141 : return false;
250 : }
251 3957 : keys_.erase(iter->second);
252 3957 : cache_.erase(iter);
253 3957 : return true;
254 : }
255 :
256 17210 : bool contains(const Key &k)
257 : {
258 17210 : Guard g(lock_);
259 34420 : return cache_.find(k) != cache_.end();
260 : }
261 :
262 0 : bool getOldestEntry(Key &kOut, Value &vOut)
263 : {
264 0 : Guard g(lock_);
265 0 : if (keys_.empty())
266 : {
267 0 : return false;
268 : }
269 0 : kOut = keys_.back().key;
270 0 : vOut = keys_.back().value;
271 0 : return true;
272 : }
273 :
274 137 : bool removeAndRecycleOldestEntry(Value &vOut)
275 : {
276 274 : Guard g(lock_);
277 137 : if (keys_.empty())
278 : {
279 1 : return false;
280 : }
281 136 : vOut = std::move(keys_.back().value);
282 136 : cache_.erase(keys_.back().key);
283 136 : keys_.pop_back();
284 136 : return true;
285 : }
286 :
287 653 : size_t getMaxSize() const
288 : {
289 653 : return maxSize_;
290 : }
291 :
292 1 : size_t getElasticity() const
293 : {
294 1 : return elasticity_;
295 : }
296 :
297 182 : size_t getMaxAllowedSize() const
298 : {
299 182 : return maxSize_ + elasticity_;
300 : }
301 :
302 29036 : template <typename F> void cwalk(F &f)
303 : {
304 57735 : Guard g(lock_);
305 29036 : std::for_each(keys_.begin(), keys_.end(), f);
306 29036 : }
307 :
308 : template <typename F> void cwalk(F &f) const
309 : {
310 : Guard g(lock_);
311 : std::for_each(keys_.begin(), keys_.end(), f);
312 : }
313 :
314 : Cache(Cache &&other)
315 : : cache_(std::move(other.cache_)), keys_(std::move(other.keys_)),
316 : maxSize_(other.maxSize_), elasticity_(other.elasticity_)
317 : {
318 : }
319 :
320 : protected:
321 33238 : size_t prune()
322 : {
323 33238 : size_t maxAllowed = maxSize_ + elasticity_;
324 33238 : if (maxSize_ == 0 || cache_.size() <= maxAllowed)
325 : { /* ERO: changed < to <= */
326 30891 : return 0;
327 : }
328 2347 : size_t count = 0;
329 18155 : while (cache_.size() > maxSize_)
330 : {
331 15808 : cache_.erase(keys_.back().key);
332 15808 : keys_.pop_back();
333 15808 : ++count;
334 : }
335 2347 : return count;
336 : }
337 :
338 : private:
339 : // Disallow copying.
340 : Cache(const Cache &) = delete;
341 : Cache &operator=(const Cache &) = delete;
342 :
343 : mutable Lock lock_{};
344 : Map cache_{};
345 : list_type keys_{};
346 : size_t maxSize_;
347 : size_t elasticity_;
348 : };
349 :
350 : } // namespace lru11
351 :
352 : /*! @endcond */
|