libboxes
boxes is a set of specialised containers built on top of STL
cache.hpp
Go to the documentation of this file.
1
11#ifndef __BOXES_CACHE_HPP__
12#define __BOXES_CACHE_HPP__
13
14#include "compiler.hpp"
15
16#include <list>
17#include <map>
18#include <memory>
19#include <unordered_map>
20
21namespace boxes {
22namespace cache {
23
24namespace policy {
25
26template <typename K, typename V> class LRU {
27public:
28 virtual ~LRU() = default;
29
38 LRU(std::size_t maxSize) : maxSize{maxSize} {}
39
40 using AccessQueue = std::list<K>;
41 using Iterator = typename AccessQueue::iterator;
42
43 struct Value {
44 V value;
45 Iterator it;
46 };
47
51 virtual void clear() { access.clear(); }
52
53 virtual void onEvict(const K &, Value &value) { access.erase(value.it); }
54
55 virtual void onAccess(const K &key, Value &value) {
56 access.erase(value.it);
57 value.it = access.insert(access.begin(), key);
58 }
59
60 virtual Value onInsert(const K &key, V value) {
61 return Value{std::move(value), access.insert(access.begin(), key)};
62 }
63
73 virtual K &front() { return access.front(); }
74
84 virtual K &back() { return access.back(); }
85
94 virtual Iterator begin() { return access.begin(); }
95
101 virtual Iterator end() { return access.end(); }
102
103 virtual const K &getKey(Iterator it) { return *it; }
104
114 virtual const K *elect() {
115 if (access.size() < maxSize) {
116 return nullptr;
117 }
118 return &this->access.back();
119 }
120
121protected:
122 const std::size_t maxSize;
123 AccessQueue access;
124};
125
131template <typename K, typename V> class NoEviction {
132public:
133 using AccessQueue = std::list<K>;
134 using Iterator = typename AccessQueue::iterator;
135
136 struct Value {
137 V value;
138 Iterator it;
139 };
140
146 const K *elect() { return nullptr; }
147
151 void clear() { access.clear(); }
152
153 void onEvict(const K &, Value &) {}
154
155 void onAccess(const K &, Value &) {}
156
157 Value onInsert(const K &key, V value) {
158 return Value{std::move(value), access.insert(access.end(), key)};
159 }
160
169 K &front() { return access.front(); }
170
179 K &back() { return access.back(); }
180
186 Iterator begin() { return access.begin(); }
187
194 Iterator end() { return access.end(); }
195
196 const K &getKey(Iterator it) { return *it; }
197
198protected:
199 AccessQueue access;
200};
201
214template <typename K, typename V> class MRU : public LRU<K, V> {
215public:
216 using LRU<K, V>::LRU;
217
218 void onAccess(const K &key, typename LRU<K, V>::Value &value) override {
219 this->access.erase(value.it);
220 value.it = this->access.insert(this->access.rbegin().base(), key);
221 }
222
223 typename LRU<K, V>::Value onInsert(const K &key, V value) override {
224 return typename LRU<K, V>::Value{
225 std::move(value),
226 this->access.insert(this->access.rbegin().base(), key)};
227 }
228};
229
238template <typename K, typename V> class LFU {
239public:
240 explicit LFU(std::size_t maxSize) : maxSize{maxSize} {}
241
242 using FrequencyMap = std::multimap<std::size_t, K>;
243 using Iterator = typename FrequencyMap::reverse_iterator;
244
245 struct Value {
246 V value;
247 Iterator it;
248 };
249
253 void clear() { freqs.clear(); }
254
255 void onAccess(const K &key, Value &value) {
256 auto it = value.it.base();
257 const auto freq = it->first;
258 freqs.erase(it);
259 value.it = std::make_reverse_iterator(freqs.emplace(freq + 1, key));
260 }
261
262 Value onInsert(const K &key, V &&value) {
263 constexpr std::size_t initialFreq = 0;
264 auto rit = std::make_reverse_iterator(freqs.emplace(initialFreq, key));
265 return Value{std::forward<V>(value), rit};
266 }
267
268 void onEvict(const K &, Value &value) { freqs.erase(value.it.base()); }
269
270 const K &getKey(Iterator it) { return it->second; }
271
279 K &front() { return freqs.rbegin()->second; }
280
288 K &back() { return freqs.begin()->second; }
289
297 Iterator begin() { return freqs.rbegin(); }
298
305 Iterator end() { return freqs.rend(); }
306
313 const K *elect() {
314 if (freqs.size() < maxSize) {
315 return nullptr;
316 }
317
318 return &freqs.begin()->second;
319 }
320
321protected:
322 const std::size_t maxSize;
323 FrequencyMap freqs;
324};
325
326} // namespace policy
327
335template <typename K, typename V,
336 template <typename, typename> class CachePolicy>
337class Cache {
338public:
339 using Policy = CachePolicy<K, V>;
340 using PolicyIterator = typename Policy::Iterator;
341
342 class iterator {
343 public:
344 using iterator_category = std::bidirectional_iterator_tag;
345 using value_type = std::pair<K, V>;
346 using difference_type = std::ptrdiff_t;
347
348 iterator(Cache<K, V, CachePolicy> &cache, PolicyIterator it)
349 : cache{cache}, it{it} {}
350
351 iterator &operator++() {
352 ++it;
353 return *this;
354 }
355
356 iterator operator++(int) {
357 iterator copy = *this;
358 ++it;
359 return copy;
360 }
361
362 iterator &operator--() {
363 --it;
364 return *this;
365 }
366
367 iterator operator--(int) {
368 iterator copy = *this;
369 --it;
370 return copy;
371 }
372
373 value_type &operator*() {
374 const auto &key = cache.policy.getKey(it);
375 const auto &pv = cache.data[key];
376 valueProxy = std::make_pair(key, pv.value);
377 return valueProxy;
378 }
379
380 value_type *operator->() {
381 const auto &key = cache.policy.getKey(it);
382 auto &pv = cache.data[key];
383 valueProxy = std::make_pair(key, pv.value);
384 return &valueProxy;
385 }
386
387 bool operator==(const iterator &other) const { return it == other.it; }
388
389 bool operator!=(const iterator &other) const { return it != other.it; }
390
391 protected:
393 PolicyIterator it;
394 std::pair<K, V> valueProxy;
395 };
396
402 explicit Cache(Policy policy) : policy{std::move(policy)} {}
403
409 void reserve(std::size_t size) { data.reserve(size); }
410
416 std::size_t size() const { return data.size(); }
417
429 std::size_t max_size() const { return data.max_size(); }
430
436 bool empty() const { return data.empty(); }
437
441 void clear() {
442 data.clear();
443 policy.clear();
444 }
445
457 template <typename KArg, typename VArg>
458 bool insert(KArg &&key, VArg &&value) {
459 if (auto it = data.find(key); it != data.end()) {
460 policy.onAccess(key, it->second);
461 return false;
462 } else {
463 // evict if needed
464 if (auto elected = policy.elect()) {
465 if (auto it = data.find(*elected); it != data.end()) {
466 policy.onEvict(*elected, it->second);
467 data.erase(it);
468 }
469 }
470
471 data.emplace(key, policy.onInsert(key, std::forward<VArg>(value)));
472 return true;
473 }
474
475 boxes_unreachable();
476 }
477
484 bool contains(const K &key) const { return data.find(key) != data.end(); }
485
493 iterator begin() { return iterator{*this, policy.begin()}; }
494
503 iterator end() { return iterator{*this, policy.end()}; }
504
510 iterator cbegin() const { return const_cast<Cache *>(this)->begin(); }
511
519 iterator cend() const { return const_cast<Cache *>(this)->end(); }
520
530 iterator find(const K &key) {
531 if (auto it = data.find(key); it != data.end()) {
532 policy.onAccess(key, it->second);
533 return iterator{*this, it->second.it};
534 }
535 return end();
536 }
537
548 V &at(const K &key) {
549 if (auto it = data.find(key); it != data.end()) {
550 policy.onAccess(key, it->second);
551 return it->second.value;
552 }
553
554 throw std::out_of_range{"Key not found"};
555 }
556
563 const V &at(const K &key) const { return const_cast<Cache *>(this)->at(key); }
564
573 K &front() { return policy.front(); }
574
583 K &back() { return policy.back(); }
584
590 const K &front() const { return const_cast<Cache *>(this)->front(); }
591
597 const K &back() const { return const_cast<Cache *>(this)->back(); }
598
599protected:
600 using Value = typename Policy::Value;
601 using Map = std::unordered_map<K, Value>;
602
603 mutable Map data;
604 mutable Policy policy;
605};
606
615template <typename K, typename V>
617 return Cache<K, V, policy::LRU>{policy::LRU<K, V>{size}};
618}
619
628template <typename K, typename V>
631}
632
640template <typename K, typename V>
643}
644
653template <typename K, typename V>
656}
657
658} // namespace cache
659} // namespace boxes
660
661#endif // __BOXES_CACHE_HPP__
Cache< K, V, policy::LRU > makeLRU(std::size_t size)
Create a new LRU cache.
Definition: cache.hpp:616
Cache< K, V, policy::MRU > makeMRU(std::size_t size)
Create a new MRU cache.
Definition: cache.hpp:629
Cache< K, V, policy::NoEviction > makeNoEviction()
Create a new no eviction cache.
Definition: cache.hpp:641
Cache< K, V, policy::LFU > makeLFU(std::size_t size)
Create a new LFU cache.
Definition: cache.hpp:654
Generic cache with pluggable eviction policies.
Definition: cache.hpp:337
iterator begin()
Returns an iterator to the first element in the cache.
Definition: cache.hpp:493
iterator end()
Returns an iterator to the element following the last element in the cache.
Definition: cache.hpp:503
Cache(Policy policy)
Construct a new Cache object.
Definition: cache.hpp:402
const V & at(const K &key) const
Returns a const reference to the value associated with the given key.
Definition: cache.hpp:563
void reserve(std::size_t size)
Reserve space for the cache to avoid unnecessary reallocations.
Definition: cache.hpp:409
bool contains(const K &key) const
Check if the cache contains a key.
Definition: cache.hpp:484
const K & front() const
Returns const reference the first element in the cache.
Definition: cache.hpp:590
K & back()
Returns the reference the last element in the cache.
Definition: cache.hpp:583
const K & back() const
Returns const reference the last element in the cache.
Definition: cache.hpp:597
std::size_t max_size() const
Returns the maximum number of elements the cache can ever hold.
Definition: cache.hpp:429
bool insert(KArg &&key, VArg &&value)
Inserts a key-value pair into the cache.
Definition: cache.hpp:458
std::size_t size() const
Returns the number of elements in the cache.
Definition: cache.hpp:416
iterator cend() const
Returns a const iterator to the element following the last element in the cache.
Definition: cache.hpp:519
V & at(const K &key)
Returns a reference to the value associated with the given key.
Definition: cache.hpp:548
iterator find(const K &key)
Returns an iterator to the element with the given key.
Definition: cache.hpp:530
iterator cbegin() const
Returns a const iterator to the first element in the cache.
Definition: cache.hpp:510
K & front()
Returns the reference the first element in the cache.
Definition: cache.hpp:573
bool empty() const
Returns whether the cache is empty.
Definition: cache.hpp:436
void clear()
Removes all elements from the cache.
Definition: cache.hpp:441
Eviction policy that evicts the least frequently used element.
Definition: cache.hpp:238
const K * elect()
Elects the least frequently used element for eviction.
Definition: cache.hpp:313
Iterator begin()
Returns an iterator to the first element in the cache.
Definition: cache.hpp:297
Iterator end()
Returns an iterator to the element following the last element in the cache.
Definition: cache.hpp:305
K & back()
Returns the reference to the last element in the cache.
Definition: cache.hpp:288
K & front()
Returns the reference to the first element in the cache.
Definition: cache.hpp:279
void clear()
Clears the eviction policy.
Definition: cache.hpp:253
Constructs a new MRU eviction policy.
Definition: cache.hpp:214
Eviction policy that never evicts elements.
Definition: cache.hpp:131
K & front()
Returns the reference to the first element in the cache.
Definition: cache.hpp:169
Iterator end()
Returns an iterator to the element following the last element in the cache.
Definition: cache.hpp:194
Iterator begin()
Returns an iterator to the first element in the cache.
Definition: cache.hpp:186
const K * elect()
Never elects an element for eviction.
Definition: cache.hpp:146
void clear()
Clears the eviction policy.
Definition: cache.hpp:151
K & back()
Returns the reference to the last element in the cache.
Definition: cache.hpp:179