Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
LRUCache.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#include "Stroika/Foundation/Containers/Common.h"
6#include "Stroika/Foundation/Cryptography/Digest/Hash.h"
8
10
11 /*
12 ********************************************************************************
13 * LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::LRUCache_::CacheIterator_ *
14 ********************************************************************************
15 */
16 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
17 struct LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::CacheIterator_ {
18 explicit CacheIterator_ (CacheElement_** start, CacheElement_** end)
19 : fCurV{start}
20 , fEndV{end}
21 , fCur{start == end ? nullptr : *fCurV}
22 {
23 }
24 CacheIterator_& operator++ ()
25 {
26 RequireNotNull (fCur);
27 Require (fCurV != fEndV);
28 fCur = fCur->fNext;
29 if (fCur == nullptr) {
30 ++fCurV;
31 if (fCurV != fEndV) {
32 fCur = *fCurV;
33 }
34 }
35 return *this;
36 }
37 optional<KeyValuePair_>& operator* ()
38 {
39 RequireNotNull (fCur);
40 return fCur->fElement;
41 }
42 optional<KeyValuePair_>* operator->()
43 {
44 RequireNotNull (fCur);
45 return &fCur->fElement;
46 }
47 bool operator== (const CacheIterator_& rhs) const
48 {
49 return fCur == rhs.fCur;
50 }
51
52 private:
53 CacheElement_** fCurV;
54 CacheElement_** fEndV;
55 CacheElement_* fCur;
56 };
57
58 /*
59 ********************************************************************************
60 * LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::CacheIterator_ *
61 ********************************************************************************
62 */
63 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
64 struct LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::CacheElement_ {
65 CacheElement_* fNext{nullptr};
66 CacheElement_* fPrev{nullptr};
67 optional<KeyValuePair_> fElement{};
68 };
69
70 /*
71 ********************************************************************************
72 ** LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>> ***
73 ********************************************************************************
74 */
75 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
81 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
82 inline LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::LRUCache (size_t maxCacheSize, const KEY_EQUALS_COMPARER& keyEqualsComparer)
83 requires (same_as<KEY_HASH_FUNCTION, nullptr_t>)
84 : fHashtableSize_{1}
85 , fKeyEqualsComparer_{keyEqualsComparer}
86 , fHashFunction_{nullptr}
87 , fCachedElts_BUF_{1}
88 , fCachedElts_First_{Memory::eUninitialized, 1}
89 , fCachedElts_Last_{Memory::eUninitialized, 1}
90 {
91 Require (maxCacheSize >= 1);
92 SetMaxCacheSize (maxCacheSize);
93 }
94 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
95 inline LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::LRUCache (size_t maxCacheSize,
96 const KEY_EQUALS_COMPARER& keyEqualsComparer,
97 size_t hashTableSize, const KEY_HASH_FUNCTION& hashFunction)
98 requires (not same_as<KEY_HASH_FUNCTION, nullptr_t>)
99 : fHashtableSize_{hashTableSize}
100 , fKeyEqualsComparer_{keyEqualsComparer}
101 , fHashFunction_{hashFunction}
102 , fCachedElts_BUF_{hashTableSize}
103 , fCachedElts_First_{Memory::eUninitialized, hashTableSize}
104 , fCachedElts_Last_{Memory::eUninitialized, hashTableSize}
105 {
106 Require (hashTableSize >= 1);
107 WeakAssert (maxCacheSize >= hashTableSize); // plausibly a bug if violated, but no biggie since SetMaxCacheSize() adjusts
108 SetMaxCacheSize (maxCacheSize);
109 }
110 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
111 inline LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::LRUCache (size_t maxCacheSize, size_t hashTableSize,
112 const KEY_HASH_FUNCTION& hashFunction)
113 requires (not same_as<KEY_HASH_FUNCTION, nullptr_t>)
114 : LRUCache{maxCacheSize, KEY_EQUALS_COMPARER{}, hashTableSize, hashFunction}
115 {
116 }
117 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
118 inline LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::LRUCache (LRUCache&& from)
119 // This is really the same as a copy, because moving is hard. This data structure contains lots of internal pointers.
120 // @todo it would make sense to do a move here. Much of the memory could be just shuffled over in many cases - but
121 // all the internal pointers would need to be patched. NOTE - important to not wrap from in move() for forward, cuz we want the lvalue version
122 : LRUCache{from}
123 {
124 }
125 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
127 requires (same_as<KEY_HASH_FUNCTION, nullptr_t>)
128 : LRUCache{from.GetMaxCacheSize (), from.GetKeyEqualsCompareFunction ()}
131 for (CacheIterator_ i = from.begin_ (); i != from.end_ (); ++i) {
132 if (*i) {
133 Add ((*i)->fKey, (*i)->fValue);
134 }
135 }
136 }
137 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
140 : LRUCache{from.GetMaxCacheSize (), from.GetKeyEqualsCompareFunction (), from.GetHashTableSize (), from.GetKeyHashFunction ()}
141 {
143 for (CacheIterator_ i = from.begin_ (); i != from.end_ (); ++i) {
144 if (*i) {
145 Add ((*i)->fKey, (*i)->fValue);
146 }
147 }
148 }
149 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
150 inline auto LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::operator= (LRUCache&& rhs) noexcept -> LRUCache&
151 {
152 IgnoreExceptionsForCall (return operator= (rhs)); // same as assign, cuz hard to move - see move constructor
153 }
154 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
155 auto LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::operator= (const LRUCache& rhs) -> LRUCache&
156 {
157 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySyncrhonized_};
158 if (this != &rhs) {
159 SetMaxCacheSize (rhs.GetMaxCacheSize ());
160 ClearCache_ ();
161 for (const auto& i : rhs.Elements ()) {
162 if (i.fKey) {
163 Add (*i.fKey, *i.fValue);
164 }
165 }
166 }
167 return *this;
168 }
169 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
170 inline size_t LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::GetMaxCacheSize () const
171 {
173 return fHashtableSize_ * fCachedElts_BUF_[0].size ();
174 }
175 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
177 {
178 Require (maxCacheSize >= 1);
180 maxCacheSize = ((maxCacheSize + fHashtableSize_ - 1) / fHashtableSize_); // divide size over number of hash chains
181 maxCacheSize = max (maxCacheSize, static_cast<size_t> (1)); // must be at least one per chain
182 for (size_t hi = 0; hi < fHashtableSize_; ++hi) {
183 if (maxCacheSize != fCachedElts_BUF_[hi].size ()) {
184 fCachedElts_BUF_[hi].resize (maxCacheSize);
185 // Initially link LRU together.
186 fCachedElts_First_[hi] = Containers::Start (fCachedElts_BUF_[hi]);
187 fCachedElts_Last_[hi] = fCachedElts_First_[hi] + maxCacheSize - 1;
188 fCachedElts_BUF_[hi][0].fPrev = nullptr;
189 for (size_t i = 0; i < maxCacheSize - 1; ++i) {
190 fCachedElts_BUF_[hi][i].fNext = fCachedElts_First_[hi] + (i + 1);
191 fCachedElts_BUF_[hi][i + 1].fPrev = fCachedElts_First_[hi] + (i);
192 }
193 fCachedElts_BUF_[hi][maxCacheSize - 1].fNext = nullptr;
194 }
195 }
196 }
197 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
199 {
201 return fKeyEqualsComparer_;
202 }
203 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
205 {
207 return fStats_;
208 }
209 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
210 inline auto LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::GetHashTableSize () const -> size_t
211 {
214 return 1;
215 }
216 else {
217 return fHashtableSize_;
218 }
219 }
220 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
221 inline auto LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::GetKeyHashFunction () const -> KEY_HASH_FUNCTION
222 {
224 return fHashFunction_;
225 }
226 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
232 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
234 {
236 optional<KeyValuePair_>* v = LookupElement_ (key);
237 if (v != nullptr) {
238 v->clear ();
240 Ensure (not Lookup (key));
241 }
242 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
244 {
246 for (auto i = begin_ (); i != end_ (); ++i) {
247 if (i->has_value () and clearPredicate ((*i)->fKey)) {
248 *i = nullopt;
249 }
250 }
251 }
252 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
255 {
256 Debug::AssertExternallySynchronizedMutex::WriteContext declareContext{fAssertExternallySyncrhonized_}; // subtle - WRITE cuz updates LRU
257 optional<KeyValuePair_>* v = LookupElement_ (key);
258 if (v == nullptr) {
259 return optional<VALUE>{};
260 }
261 Ensure (fKeyEqualsComparer_ (key, (*v)->fKey));
262 return (*v)->fValue;
263 }
264 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
272 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
274 requires (same_as<KEY, VALUE>)
275 {
276 Add (key, key);
277 }
278 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
279 inline size_t LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::H_ ([[maybe_unused]] typename Common::ArgByValueType<KEY> k) const
280 {
281 Assert (fHashtableSize_ >= 1);
283 return 0; // avoid referencing hash function
284 }
285 else {
286 return fHashFunction_ (k) % fHashtableSize_;
287 }
288 }
289 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
291 {
294 for (CacheIterator_ i = begin_ (); i != end_ (); ++i) {
295 if (*i) {
296 result.Add ((*i)->fKey, (*i)->fValue);
297 }
298 }
299 return result;
300 }
301 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
304 {
305 auto v = Lookup (key);
306 if (v.has_value ()) {
307 return *v;
308 }
309 else {
311 Add (key, newV);
312 return newV;
313 }
314 }
315 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
317 {
318 LRUCache* ncThis = const_cast<LRUCache*> (this); // http://stroika-bugs.sophists.com/browse/STK-764
319 return CacheIterator_{std::begin (ncThis->fCachedElts_First_), std::end (ncThis->fCachedElts_First_)};
320 }
321 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
322 inline typename LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::CacheIterator_
323 LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::end_ () const
324 {
325 return CacheIterator_{nullptr, nullptr};
326 }
327 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
328 inline void LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::ShuffleToHead_ (size_t chainIdx, CacheElement_* b)
329 {
330 Require (chainIdx < fHashtableSize_);
332 if (b == fCachedElts_First_[chainIdx]) {
333 Assert (b->fPrev == nullptr);
334 return; // already at head
335 }
336 CacheElement_* prev = b->fPrev;
337 AssertNotNull (prev); // don't call this if already at head
338 // patch following and preceeding blocks to point to each other
339 prev->fNext = b->fNext;
340 if (b->fNext == nullptr) {
341 Assert (b == fCachedElts_Last_[chainIdx]);
342 fCachedElts_Last_[chainIdx] = b->fPrev;
343 }
344 else {
345 b->fNext->fPrev = prev;
346 }
347
348 // Now patch us into the head of the list
349 CacheElement_* oldFirst = fCachedElts_First_[chainIdx];
351 b->fNext = oldFirst;
352 oldFirst->fPrev = b;
353 b->fPrev = nullptr;
354 fCachedElts_First_[chainIdx] = b;
355
356 Ensure (fCachedElts_Last_[chainIdx] != nullptr and fCachedElts_Last_[chainIdx]->fNext == nullptr);
357 Ensure (fCachedElts_First_[chainIdx] != nullptr and fCachedElts_First_[chainIdx] == b and
358 fCachedElts_First_[chainIdx]->fPrev == nullptr and fCachedElts_First_[chainIdx]->fNext != nullptr);
359 }
360 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
361 inline void LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::ClearCache_ ()
362 {
363 for (size_t hi = 0; hi < fHashtableSize_; ++hi) {
364 for (CacheElement_* cur = fCachedElts_First_[hi]; cur != nullptr; cur = cur->fNext) {
365 cur->fElement = nullopt;
366 }
367 }
368 }
369 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
370 inline auto LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::LookupElement_ (typename Common::ArgByValueType<KeyType> item)
371 -> optional<KeyValuePair_>*
372 {
373 size_t chainIdx = H_ (item);
374 Assert (0 <= chainIdx and chainIdx < fHashtableSize_);
375 for (CacheElement_* cur = fCachedElts_First_[chainIdx]; cur != nullptr; cur = cur->fNext) {
376 if (cur->fElement and fKeyEqualsComparer_ (cur->fElement->fKey, item)) {
377 ShuffleToHead_ (chainIdx, cur);
378 fStats_.IncrementHits ();
379 return &fCachedElts_First_[chainIdx]->fElement;
380 }
381 }
382 fStats_.IncrementMisses ();
383 return nullptr;
384 }
385 template <typename KEY, typename VALUE, typename KEY_EQUALS_COMPARER, typename KEY_HASH_FUNCTION, typename STATS_TYPE>
386 inline auto LRUCache<KEY, VALUE, KEY_EQUALS_COMPARER, KEY_HASH_FUNCTION, STATS_TYPE>::AddNew_ (typename Common::ArgByValueType<KeyType> item)
387 -> optional<KeyValuePair_>*
388 {
389 size_t chainIdx = H_ (item);
390 Assert (0 <= chainIdx and chainIdx < fHashtableSize_);
391 ShuffleToHead_ (chainIdx, fCachedElts_Last_[chainIdx]);
392 return &fCachedElts_First_[chainIdx]->fElement;
393 }
394
395}
#define AssertNotNull(p)
Definition Assertions.h:333
#define RequireNotNull(p)
Definition Assertions.h:347
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:438
LRUCache implements a simple least-recently-used caching strategy, with optional hashing (of keys) to...
Definition LRUCache.h:94
nonvirtual Containers::Mapping< KEY, VALUE > Elements() const
Definition LRUCache.inl:290
nonvirtual void Add(typename Common::ArgByValueType< KEY > key, typename Common::ArgByValueType< VALUE > value)
Definition LRUCache.inl:265
nonvirtual void SetMaxCacheSize(size_t maxCacheSize)
Definition LRUCache.inl:176
nonvirtual VALUE LookupValue(typename Common::ArgByValueType< KEY > key, const function< VALUE(typename Common::ArgByValueType< KEY >)> &valueFetcher)
Definition LRUCache.inl:302
nonvirtual optional< VALUE > Lookup(typename Common::ArgByValueType< KEY > key)
Definition LRUCache.inl:253
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:190
shared_lock< const AssertExternallySynchronizedMutex > ReadContext
Instantiate AssertExternallySynchronizedMutex::ReadContext to designate an area of code where protect...
unique_lock< AssertExternallySynchronizedMutex > WriteContext
Instantiate AssertExternallySynchronizedMutex::WriteContext to designate an area of code where protec...
nonvirtual size_t size() const noexcept
nonvirtual void resize(size_t nElements)
conditional_t<(sizeof(CHECK_T)<=2 *sizeof(void *)) and is_trivially_copyable_v< CHECK_T >, CHECK_T, const CHECK_T & > ArgByValueType
This is an alias for 'T' - but how we want to pass it on stack as formal parameter.
Definition TypeHints.h:32
CONTAINER::value_type * Start(CONTAINER &c)
For a contiguous container (such as a vector or basic_string) - find the pointer to the start of the ...