1 #ifndef UTILITY_LRU_CACHE_HPP
2 #define UTILITY_LRU_CACHE_HPP
4 #include <fmt/format.h>
5 #include <fmt/ostream.h>
6 #include <fmt/ranges.h>
13 #include <type_traits>
14 #include <unordered_map>
41 template <
class KeyType,
class ValueType,
class Hash = std::hash<KeyType>>
46 using Value = ValueType;
56 const Value*
lookup(
const Key& key) {
58 auto it = cache_.find(key);
59 if (it == cache_.end()) {
66 auto& cached_it = it->second.iter;
67 if (cached_it != used_.begin()) {
69 used_.erase(cached_it);
70 used_.push_front(&it->first);
71 cached_it = used_.begin();
75 return &it->second.value;
84 const Value&
insert(
const Key& k,
const Value& v) {
85 return this->insert_impl(k, v);
88 const Value&
insert(Key&& k,
const Value& v) {
89 return this->insert_impl(std::move(k), v);
92 const Value&
insert(
const Key& k, Value&& v) {
93 return this->insert_impl(k, std::move(v));
96 const Value&
insert(Key&& k, Value&& v) {
97 return this->insert_impl(std::move(k), std::move(v));
103 const auto it = cache_.find(k);
104 if (it == cache_.end()) {
107 assert(it != cache_.end() &&
"Trying to erase from cache non-existant key");
108 used_.erase(it->second.second);
116 template <
class K,
class V>
const Value& insert_impl(K&& key, V&& val) {
117 const auto it = cache_.find(key);
118 if (it != cache_.end()) {
119 assert(val == it->second.value &&
120 "We assume a key always maps to the same val");
123 return it->second.value;
126 if (used_.size() >= capacity_) {
127 assert(capacity_ > 0);
128 const auto* remove_key = used_.back();
130 cache_.erase(*remove_key);
144 auto [i, b] = cache_.emplace(
145 std::forward<K>(key),
146 typename Map::mapped_type{std::forward<V>(val), ListIteratorType{}});
149 static_cast<void>(b);
150 used_.push_front(&i->first);
151 i->second.iter = used_.begin();
152 return i->second.value;
155 using List = std::list<const Key*>;
156 using ListIteratorType =
typename List::iterator;
158 std::unordered_map<Key, ValueIteratorPair<Value, ListIteratorType>, Hash,
159 std::equal_to<void>>;
162 bool empty()
const {
return cache_.empty(); }
163 std::size_t size()
const {
return cache_.size(); }
164 std::size_t capacity()
const {
return capacity_; }
170 LruCache(LruCache&& cache) =
default;
173 friend void swap(LruCache& c0, LruCache& c1) {
176 swap(c0.used_, c1.used_);
177 swap(c0.cache_, c1.cache_);
178 swap(c0.capacity_, c1.capacity_);
179 swap(c0.print_hitrate_on_destruction_, c1.print_hitrate_on_destruction_);
180 swap(c0.hits_, c1.hits_);
181 swap(c0.misses_, c1.misses_);
191 : capacity_(other.capacity_),
192 print_hitrate_on_destruction_(other.print_hitrate_on_destruction_),
193 hits_(other.hits_), misses_(other.misses_) {
196 std::is_copy_constructible_v<Key>,
197 "Copy constructor for LruCache disabled as Key is not copyable");
199 std::is_copy_constructible_v<Value>,
200 "Copy constructor for LruCache disabled as Value is not copyable");
203 cache_.reserve(other.size());
205 for (
auto it = other.used_.rbegin(); it != other.used_.rend(); ++it) {
206 const auto& other_k = **it;
208 const auto& other_v = other.cache_.at(other_k).value;
209 this->
insert(other_k, other_v);
211 assert(other.used_.size() == used_.size());
212 auto other_it = other.used_.begin();
213 for ([[maybe_unused]]
const auto* key : used_) {
214 assert(key != *other_it++ &&
"Pointers should not be the same");
215 assert(*key == **other_it++ &&
"Keys should be identical after copy");
217 assert(used_.size() == cache_.size());
218 assert(capacity() == other.capacity());
232 LruCache() =
default;
234 explicit LruCache(
const std::size_t capacity) : capacity_(capacity) {
235 cache_.reserve(capacity);
238 explicit LruCache(
const std::size_t capacity,
239 const bool print_hitrate_on_destruction)
240 : capacity_(capacity),
241 print_hitrate_on_destruction_(print_hitrate_on_destruction) {
242 cache_.reserve(capacity);
246 if (hits_ > 0 || misses_ > 0) {
247 fmt::print(
"LruCache destructor summary: Hits {} / misses {} - {}%\n",
249 static_cast<double>(hits_) /
static_cast<double>(misses_) *
255 template <
class K,
class V>
256 friend std::ostream& operator<<(std::ostream& os,
257 const LruCache<K, V>& cache);
261 std::size_t capacity_ = 64;
263 bool print_hitrate_on_destruction_ =
false;
264 std::size_t hits_ = 0;
265 std::size_t misses_ = 0;
268 template <
class K,
class V>
269 std::ostream& operator<<(std::ostream& os,
const LruCache<K, V>& cache) {
270 fmt::memory_buffer b{};
271 for (
const auto& [k, v] : cache.cache_) {
272 fmt::format_to(b,
"{}: {}, ", k, v.value);
275 b.resize(b.size() - 2);
277 return os << fmt::to_string(b);
Quick and dirty attempt at implementing an LRU cache.
Definition: lru_cache.hpp:42
const Value & insert(Key &&k, Value &&v)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: lru_cache.hpp:96
void erase(const Key &k)
Remove a key k from the cache, if it exists.
Definition: lru_cache.hpp:101
LruCache(const LruCache &other)
Copy constructor.
Definition: lru_cache.hpp:190
const Value & insert(const Key &k, Value &&v)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: lru_cache.hpp:92
const Value & insert(const Key &k, const Value &v)
Insert a key k and a value v.
Definition: lru_cache.hpp:84
const Value & insert(Key &&k, const Value &v)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: lru_cache.hpp:88
const Value * lookup(const Key &key)
Lookup a value given a key.
Definition: lru_cache.hpp:56
Utility functions.
Definition: flat_char_value_map.hpp:13
std::pair neater replacement with appropriate names
Definition: lru_cache.hpp:20