Directory: | cvmfs/ |
---|---|
File: | cvmfs/lru.h |
Date: | 2025-04-20 02:34:28 |
Exec | Total | Coverage | |
---|---|---|---|
Lines: | 321 | 325 | 98.8% |
Branches: | 135 | 246 | 54.9% |
Line | Branch | Exec | Source |
---|---|---|---|
1 | /** | ||
2 | * This file is part of the CernVM File System. | ||
3 | * | ||
4 | * This class provides an Least Recently Used (LRU) cache for arbitrary data | ||
5 | * It stores Key-Value pairs of arbitrary data types in a hash table and | ||
6 | * automatically deletes the entries which are least touched in the last time | ||
7 | * to prevent the structure from growing beyond a given maximal cache size. | ||
8 | * The cache uses a hand crafted memory allocator to use memory efficiently | ||
9 | * | ||
10 | * Hash functions have to be provided. They should return an equal distribution | ||
11 | * of keys in uint32_t. In addition, a special key has to be provided that is | ||
12 | * used to mark "empty" elements in the hash table. | ||
13 | * | ||
14 | * The cache size has to be a multiply of 64. | ||
15 | * | ||
16 | * usage: | ||
17 | * // 100 entries, -1 special key | ||
18 | * LruCache<int, string> cache(100, -1, hasher_int); | ||
19 | * | ||
20 | * // Inserting some stuff | ||
21 | * cache.insert(42, "fourtytwo"); | ||
22 | * cache.insert(2, "small prime number"); | ||
23 | * cache.insert(1337, "leet"); | ||
24 | * | ||
25 | * // Trying to retrieve a value | ||
26 | * int result; | ||
27 | * if (cache.lookup(21, result)) { | ||
28 | * cout << "cache hit: " << result << endl; | ||
29 | * } else { | ||
30 | * cout << "cache miss" << endl; | ||
31 | * } | ||
32 | * | ||
33 | * cache.drop(); // Empty the cache | ||
34 | */ | ||
35 | |||
36 | #ifndef CVMFS_LRU_H_ | ||
37 | #define CVMFS_LRU_H_ | ||
38 | |||
39 | // If defined the cache is secured by a posix mutex | ||
40 | #define LRU_CACHE_THREAD_SAFE | ||
41 | |||
42 | #include <stdint.h> | ||
43 | |||
44 | #include <algorithm> | ||
45 | #include <cassert> | ||
46 | #include <cstring> | ||
47 | #include <functional> | ||
48 | #include <map> | ||
49 | #include <string> | ||
50 | |||
51 | #include "smallhash.h" | ||
52 | #include "statistics.h" | ||
53 | #include "util/atomic.h" | ||
54 | #include "util/platform.h" | ||
55 | #include "util/single_copy.h" | ||
56 | #include "util/smalloc.h" | ||
57 | |||
58 | namespace lru { | ||
59 | |||
60 | /** | ||
61 | * Counting of cache operations. | ||
62 | */ | ||
63 | struct Counters { | ||
64 | perf::Counter *sz_size; | ||
65 | perf::Counter *n_hit; | ||
66 | perf::Counter *n_miss; | ||
67 | perf::Counter *n_insert; | ||
68 | perf::Counter *n_insert_negative; | ||
69 | uint64_t num_collisions; | ||
70 | uint32_t max_collisions; | ||
71 | perf::Counter *n_update; | ||
72 | perf::Counter *n_update_value; | ||
73 | perf::Counter *n_replace; | ||
74 | perf::Counter *n_forget; | ||
75 | perf::Counter *n_drop; | ||
76 | perf::Counter *sz_allocated; | ||
77 | |||
78 | 129 | explicit Counters(perf::StatisticsTemplate statistics) { | |
79 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | sz_size = statistics.RegisterTemplated("sz_size", "Total size"); |
80 | 129 | num_collisions = 0; | |
81 | 129 | max_collisions = 0; | |
82 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_hit = statistics.RegisterTemplated("n_hit", "Number of hits"); |
83 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_miss = statistics.RegisterTemplated("n_miss", "Number of misses"); |
84 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_insert = statistics.RegisterTemplated("n_insert", "Number of inserts"); |
85 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_insert_negative = statistics.RegisterTemplated("n_insert_negative", |
86 | "Number of negative inserts"); | ||
87 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_update = statistics.RegisterTemplated("n_update", |
88 | "Number of updates"); | ||
89 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_update_value = statistics.RegisterTemplated("n_update_value", |
90 | "Number of value changes"); | ||
91 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_replace = statistics.RegisterTemplated("n_replace", "Number of replaces"); |
92 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_forget = statistics.RegisterTemplated("n_forget", "Number of forgets"); |
93 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | n_drop = statistics.RegisterTemplated("n_drop", "Number of drops"); |
94 |
3/6✓ Branch 2 taken 129 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
✓ Branch 9 taken 129 times.
✗ Branch 10 not taken.
|
129 | sz_allocated = statistics.RegisterTemplated("sz_allocated", |
95 | "Number of allocated bytes "); | ||
96 | 129 | } | |
97 | }; | ||
98 | |||
99 | |||
100 | /** | ||
101 | * Template class to create a LRU cache | ||
102 | * @param Key type of the key values | ||
103 | * @param Value type of the value values | ||
104 | */ | ||
105 | template<class Key, class Value> | ||
106 | class LruCache : SingleCopy { | ||
107 | private: | ||
108 | // Forward declarations of private internal data structures | ||
109 | template<class T> class ListEntry; | ||
110 | template<class T> class ListEntryHead; | ||
111 | template<class T> class ListEntryContent; | ||
112 | template<class M> class MemoryAllocator; | ||
113 | |||
114 | // Helpers to get the template magic right | ||
115 | typedef ListEntryContent<Key> ConcreteListEntryContent; | ||
116 | typedef MemoryAllocator<ConcreteListEntryContent> ConcreteMemoryAllocator; | ||
117 | |||
118 | /** | ||
119 | * This structure wraps the user data and relates it to the LRU list entry | ||
120 | */ | ||
121 | typedef struct { | ||
122 | ListEntryContent<Key> *list_entry; | ||
123 | Value value; | ||
124 | } CacheEntry; | ||
125 | |||
126 | // static uint64_t GetEntrySize() { return sizeof(Key) + sizeof(Value); } | ||
127 | |||
128 | /** | ||
129 | * A special purpose memory allocator for the cache entries. | ||
130 | * It allocates enough memory for the maximal number of cache entries at | ||
131 | * startup, and assigns new ListEntryContent objects to a free spot in this | ||
132 | * memory pool (by overriding the 'new' and 'delete' operators of | ||
133 | * ListEntryContent). | ||
134 | * | ||
135 | * @param T the type of object to be allocated by this MemoryAllocator | ||
136 | */ | ||
137 | template<class T> class MemoryAllocator : SingleCopy { | ||
138 | public: | ||
139 | /** | ||
140 | * Creates a MemoryAllocator to handle a memory pool for objects of type T | ||
141 | * @param num_slots the number of slots to be allocated for the given datatype T | ||
142 | */ | ||
143 | 184 | explicit MemoryAllocator(const unsigned int num_slots) { | |
144 | // how many bitmap chunks (chars) do we need? | ||
145 | 184 | unsigned int num_bytes_bitmap = num_slots / 8; | |
146 | 184 | bits_per_block_ = 8 * sizeof(bitmap_[0]); | |
147 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
|
184 | assert((num_slots % bits_per_block_) == 0); |
148 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
|
184 | assert(num_slots >= 2*bits_per_block_); |
149 | |||
150 | // How much actual memory do we need? | ||
151 | 184 | const unsigned int num_bytes_memory = sizeof(T) * num_slots; | |
152 | |||
153 | // Allocate zero'd memory | ||
154 | 184 | bitmap_ = reinterpret_cast<uint64_t *>(scalloc(num_bytes_bitmap, 1)); | |
155 | 184 | memory_ = reinterpret_cast<T *>(scalloc(num_bytes_memory, 1)); | |
156 | |||
157 | // Create initial state | ||
158 | 184 | num_slots_ = num_slots; | |
159 | 184 | num_free_slots_ = num_slots; | |
160 | 184 | next_free_slot_ = 0; | |
161 | 184 | bytes_allocated_ = num_bytes_bitmap + num_bytes_memory; | |
162 | 184 | } | |
163 | |||
164 | /** | ||
165 | * Number of bytes for a single entry | ||
166 | */ | ||
167 | 96 | static double GetEntrySize() { | |
168 | 96 | return static_cast<double>(sizeof(T)) + 1.0/8.0; | |
169 | } | ||
170 | |||
171 | /** | ||
172 | * The memory allocator also frees all allocated data | ||
173 | */ | ||
174 | 258 | virtual ~MemoryAllocator() { | |
175 | 258 | free(bitmap_); | |
176 | 258 | free(memory_); | |
177 | } | ||
178 | |||
179 | /** | ||
180 | * Check if the memory pool is full. | ||
181 | * @return true if all slots are occupied, otherwise false | ||
182 | */ | ||
183 | 8638 | inline bool IsFull() const { return num_free_slots_ == 0; } | |
184 | |||
185 | 4319 | T* Construct(const T object) { | |
186 | 4319 | T* mem = Allocate(); | |
187 |
1/2✓ Branch 0 taken 4302 times.
✗ Branch 1 not taken.
|
4319 | if (mem != NULL) { |
188 |
1/2✓ Branch 2 taken 4302 times.
✗ Branch 3 not taken.
|
4319 | new (static_cast<void*>(mem)) T(object); |
189 | } | ||
190 | 4319 | return mem; | |
191 | } | ||
192 | |||
193 | 4319 | void Destruct(T *object) { | |
194 | 4319 | object->~T(); | |
195 | 4319 | Deallocate(object); | |
196 | 4319 | } | |
197 | |||
198 | /** | ||
199 | * Allocate a slot and returns a pointer to the memory. | ||
200 | * @return a pointer to a chunk of the memory pool | ||
201 | */ | ||
202 | 4319 | T* Allocate() { | |
203 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4302 times.
|
4319 | if (this->IsFull()) |
204 | ✗ | return NULL; | |
205 | |||
206 | // Allocate a slot | ||
207 | 4319 | this->SetBit(next_free_slot_); | |
208 | 4319 | --num_free_slots_; | |
209 | 4319 | T *slot = memory_ + next_free_slot_; | |
210 | |||
211 | // Find a new free slot if there are some left | ||
212 |
2/2✓ Branch 1 taken 3280 times.
✓ Branch 2 taken 1022 times.
|
4319 | if (!this->IsFull()) { |
213 | 3297 | unsigned bitmap_block = next_free_slot_ / bits_per_block_; | |
214 |
2/2✓ Branch 0 taken 46 times.
✓ Branch 1 taken 3280 times.
|
3343 | while (~bitmap_[bitmap_block] == 0) |
215 | 46 | bitmap_block = (bitmap_block + 1) % (num_slots_ / bits_per_block_); | |
216 | // TODO(jblomer): faster search inside the int | ||
217 | 3297 | next_free_slot_ = bitmap_block * bits_per_block_; | |
218 |
2/2✓ Branch 1 taken 99814 times.
✓ Branch 2 taken 3280 times.
|
103148 | while (this->GetBit(next_free_slot_)) |
219 | 99851 | next_free_slot_++; | |
220 | } | ||
221 | |||
222 | 4319 | return slot; | |
223 | } | ||
224 | |||
225 | /** | ||
226 | * Free a given slot in the memory pool | ||
227 | * @param slot a pointer to the slot be freed | ||
228 | */ | ||
229 | 4319 | void Deallocate(T* slot) { | |
230 | // Check if given slot is in bounds | ||
231 |
2/4✓ Branch 0 taken 4302 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4302 times.
✗ Branch 3 not taken.
|
4319 | assert((slot >= memory_) && (slot <= memory_ + num_slots_)); |
232 | |||
233 | // Get position of slot | ||
234 | 4319 | const unsigned int position = slot - memory_; | |
235 | |||
236 | // Check if slot was already freed | ||
237 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4302 times.
|
4319 | assert(this->GetBit(position)); |
238 | |||
239 | // Free slot, save the position of this slot as free (faster reallocation) | ||
240 | 4319 | this->UnsetBit(position); | |
241 | 4319 | next_free_slot_ = position; | |
242 | 4319 | ++num_free_slots_; | |
243 | 4319 | } | |
244 | |||
245 | 186 | uint64_t bytes_allocated() { return bytes_allocated_; } | |
246 | |||
247 | private: | ||
248 | /** | ||
249 | * Check a bit in the internal allocation bitmap. | ||
250 | * @param position the position to check | ||
251 | * @return true if bit is set, otherwise false | ||
252 | */ | ||
253 | 107467 | inline bool GetBit(const unsigned position) { | |
254 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 107396 times.
|
107467 | assert(position < num_slots_); |
255 | 107467 | return ((bitmap_[position / bits_per_block_] & | |
256 | 107467 | (uint64_t(1) << (position % bits_per_block_))) != 0); | |
257 | } | ||
258 | |||
259 | /** | ||
260 | * set a bit in the internal allocation bitmap | ||
261 | * @param position the number of the bit to be set | ||
262 | */ | ||
263 | 4319 | inline void SetBit(const unsigned position) { | |
264 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4302 times.
|
4319 | assert(position < num_slots_); |
265 | 4319 | bitmap_[position / bits_per_block_] |= | |
266 | 4319 | uint64_t(1) << (position % bits_per_block_); | |
267 | 4319 | } | |
268 | |||
269 | /** | ||
270 | * Clear a bit in the internal allocation bitmap | ||
271 | * @param position the number of the bit to be cleared | ||
272 | */ | ||
273 | 4319 | inline void UnsetBit(const unsigned position) { | |
274 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4302 times.
|
4319 | assert(position < num_slots_); |
275 | 4319 | bitmap_[position / bits_per_block_] &= | |
276 | 4319 | ~(uint64_t(1) << (position % bits_per_block_)); | |
277 | 4319 | } | |
278 | |||
279 | unsigned int num_slots_; /**< Overall number of slots in memory pool. */ | ||
280 | unsigned int num_free_slots_; /**< Current number of free slots left. */ | ||
281 | unsigned int next_free_slot_; /**< Position of next free slot in pool. */ | ||
282 | uint64_t bytes_allocated_; | ||
283 | uint64_t *bitmap_; /**< A bitmap to mark slots as allocated. */ | ||
284 | unsigned bits_per_block_; | ||
285 | T *memory_; /**< The memory pool, array of Ms. */ | ||
286 | }; | ||
287 | |||
288 | |||
289 | /** | ||
290 | * Internal LRU list entry, to maintain the doubly linked list. | ||
291 | * The list keeps track of the least recently used keys in the cache. | ||
292 | */ | ||
293 | template<class T> class ListEntry { | ||
294 | friend class LruCache; | ||
295 | public: | ||
296 | /** | ||
297 | * Create a new list entry as lonely, both next and prev pointing to this. | ||
298 | */ | ||
299 | 4503 | ListEntry() { | |
300 | 4503 | this->next = this; | |
301 | 4503 | this->prev = this; | |
302 | 4503 | } | |
303 | |||
304 | 4319 | ListEntry(const ListEntry<T> &other) { | |
305 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4302 times.
|
4319 | next = (other.next == &other) ? this : other.next; |
306 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 4302 times.
|
4319 | prev = (other.prev == &other) ? this : other.prev; |
307 | 4319 | } | |
308 | |||
309 | 17466 | virtual ~ListEntry() {} | |
310 | |||
311 | /** | ||
312 | * Checks if the ListEntry is the list head | ||
313 | * @return true if ListEntry is list head otherwise false | ||
314 | */ | ||
315 | virtual bool IsListHead() const = 0; | ||
316 | |||
317 | /** | ||
318 | * A lonely ListEntry has no connection to other elements. | ||
319 | * @return true if ListEntry is lonely otherwise false | ||
320 | */ | ||
321 |
3/4✓ Branch 0 taken 50618 times.
✓ Branch 1 taken 145407 times.
✓ Branch 2 taken 50618 times.
✗ Branch 3 not taken.
|
196107 | bool IsLonely() const { return (this->next == this && this->prev == this); } |
322 | |||
323 | ListEntry<T> *next; /**< Pointer to next element in the list. */ | ||
324 | ListEntry<T> *prev; /**< Pointer to previous element in the list. */ | ||
325 | |||
326 | protected: | ||
327 | /** | ||
328 | * Insert a given ListEntry after this one. | ||
329 | * @param entry the ListEntry to insert after this one | ||
330 | */ | ||
331 | inline void InsertAsSuccessor(ListEntryContent<T> *entry) { | ||
332 | assert(entry->IsLonely()); | ||
333 | |||
334 | // Mount the new element between this and this->next | ||
335 | entry->next = this->next; | ||
336 | entry->prev = this; | ||
337 | |||
338 | // Fix pointers of existing list elements | ||
339 | this->next->prev = entry; | ||
340 | this->next = entry; | ||
341 | assert(!entry->IsLonely()); | ||
342 | } | ||
343 | |||
344 | /** | ||
345 | * Insert a given ListEntry in front of this one | ||
346 | * @param entry the ListEntry to insert in front of this one | ||
347 | */ | ||
348 | 50647 | inline void InsertAsPredecessor(ListEntryContent<T> *entry) { | |
349 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 50618 times.
|
50647 | assert(entry->IsLonely()); |
350 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 50618 times.
|
50647 | assert(!entry->IsListHead()); |
351 | |||
352 | // Mount the new element between this and this->prev | ||
353 | 50647 | entry->next = this; | |
354 | 50647 | entry->prev = this->prev; | |
355 | |||
356 | // Fix pointers of existing list elements | ||
357 | 50647 | this->prev->next = entry; | |
358 | 50647 | this->prev = entry; | |
359 | |||
360 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 50618 times.
|
50647 | assert(!entry->IsLonely()); |
361 | 50647 | } | |
362 | |||
363 | /** | ||
364 | * Remove this element from it's list. | ||
365 | * The function connects this->next with this->prev leaving the list | ||
366 | * in a consistent state. The ListEntry itself is lonely afterwards, | ||
367 | * but not deleted. | ||
368 | */ | ||
369 | virtual void RemoveFromList() = 0; | ||
370 | |||
371 | private: | ||
372 | // No assignment operator (enforced by linker error) | ||
373 | ListEntry<T>& operator=(const ListEntry<T> &other); | ||
374 | }; // template<class T> class ListEntry | ||
375 | |||
376 | /** | ||
377 | * Specialized ListEntry to contain a data entry of type T | ||
378 | */ | ||
379 | template<class T> class ListEntryContent : public ListEntry<T> { | ||
380 | public: | ||
381 |
1/2✓ Branch 2 taken 157 times.
✗ Branch 3 not taken.
|
4319 | explicit ListEntryContent(T content) { |
382 | 4319 | content_ = content; | |
383 | 4319 | } | |
384 | |||
385 | 55188 | inline bool IsListHead() const { return false; } | |
386 | 1239 | inline T content() const { return content_; } | |
387 | |||
388 | /** | ||
389 | * See ListEntry base class. | ||
390 | */ | ||
391 | 47466 | inline void RemoveFromList() { | |
392 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 47454 times.
|
47466 | assert(!this->IsLonely()); |
393 | |||
394 | // Remove this from list | ||
395 | 47466 | this->prev->next = this->next; | |
396 | 47466 | this->next->prev = this->prev; | |
397 | |||
398 | // Make this lonely | ||
399 | 47466 | this->next = this; | |
400 | 47466 | this->prev = this; | |
401 | 47466 | } | |
402 | |||
403 | private: | ||
404 | T content_; /**< The data content of this ListEntry */ | ||
405 | }; | ||
406 | |||
407 | /** | ||
408 | * Specialized ListEntry to form a list head. | ||
409 | * Every list has exactly one list head which is also the entry point | ||
410 | * in the list. It is used to manipulate the list. | ||
411 | */ | ||
412 | template<class T> class ListEntryHead : public ListEntry<T> { | ||
413 | public: | ||
414 | 184 | explicit ListEntryHead(ConcreteMemoryAllocator *allocator) : | |
415 | 184 | allocator_(allocator) {} | |
416 | |||
417 | 258 | virtual ~ListEntryHead() { | |
418 | 258 | this->clear(); | |
419 | } | ||
420 | |||
421 | /** | ||
422 | * Remove all entries from the list. | ||
423 | * ListEntry objects are deleted but contained data keeps available | ||
424 | */ | ||
425 | 186 | void clear() { | |
426 | // Delete all list entries | ||
427 | 186 | ListEntry<T> *entry = this->next; | |
428 | ListEntry<T> *delete_me; | ||
429 |
2/2✓ Branch 1 taken 3164 times.
✓ Branch 2 taken 131 times.
|
3367 | while (!entry->IsListHead()) { |
430 | 3181 | delete_me = entry; | |
431 | 3181 | entry = entry->next; | |
432 | 3181 | allocator_->Destruct(static_cast<ConcreteListEntryContent*>(delete_me)); | |
433 | } | ||
434 | |||
435 | // Reset the list to lonely | ||
436 | 186 | this->next = this; | |
437 | 186 | this->prev = this; | |
438 | 186 | } | |
439 | |||
440 | 191 | inline bool IsListHead() const { return true; } | |
441 | 1019 | inline bool IsEmpty() const { return this->IsLonely(); } | |
442 | |||
443 | /** | ||
444 | * Push a new data object to the end of the list. | ||
445 | * @param the data object to insert | ||
446 | * @return the ListEntryContent structure wrapped around the data object | ||
447 | */ | ||
448 | 4319 | inline ListEntryContent<T>* PushBack(T content) { | |
449 | ListEntryContent<T> *new_entry = | ||
450 |
1/2✓ Branch 2 taken 4302 times.
✗ Branch 3 not taken.
|
4319 | allocator_->Construct(ListEntryContent<T>(content)); |
451 | 4319 | this->InsertAsPredecessor(new_entry); | |
452 | 4319 | return new_entry; | |
453 | } | ||
454 | |||
455 | /** | ||
456 | * Pop the first object of the list. | ||
457 | * The object is returned and removed from the list | ||
458 | * @return the data object which resided in the first list entry | ||
459 | */ | ||
460 | 1019 | inline T PopFront() { | |
461 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1019 times.
|
1019 | assert(!this->IsEmpty()); |
462 | 1019 | return Pop(this->next); | |
463 | } | ||
464 | |||
465 | /** | ||
466 | * Take a list entry out of it's list and reinsert at the end of this list. | ||
467 | * @param the ListEntry to be moved to the end of this list | ||
468 | */ | ||
469 | 46328 | inline void MoveToBack(ListEntryContent<T> *entry) { | |
470 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 46316 times.
|
46328 | assert(!entry->IsLonely()); |
471 | |||
472 | 46328 | entry->RemoveFromList(); | |
473 | 46328 | this->InsertAsPredecessor(entry); | |
474 | 46328 | } | |
475 | |||
476 | /** | ||
477 | * See ListEntry base class | ||
478 | */ | ||
479 | ✗ | inline void RemoveFromList() { assert(false); } | |
480 | |||
481 | private: | ||
482 | /** | ||
483 | * Pop a ListEntry from the list (arbitrary position). | ||
484 | * The given ListEntry is removed from the list, deleted and it's | ||
485 | * data content is returned | ||
486 | * @param popped_entry the entry to be popped | ||
487 | * @return the data object of the popped ListEntry | ||
488 | */ | ||
489 | 1019 | inline T Pop(ListEntry<T> *popped_entry) { | |
490 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1019 times.
|
1019 | assert(!popped_entry->IsListHead()); |
491 | |||
492 | 1019 | ListEntryContent<T> *popped = (ListEntryContent<T> *)popped_entry; | |
493 | 1019 | popped->RemoveFromList(); | |
494 | 1019 | T result = popped->content(); | |
495 | 1019 | allocator_->Destruct(static_cast<ConcreteListEntryContent*>( | |
496 | popped_entry)); | ||
497 | 1019 | return result; | |
498 | } | ||
499 | |||
500 | private: | ||
501 | ConcreteMemoryAllocator *allocator_; | ||
502 | }; | ||
503 | |||
504 | public: // LruCache | ||
505 | /** | ||
506 | * Create a new LRU cache object | ||
507 | * @param cache_size the maximal size of the cache | ||
508 | */ | ||
509 | 184 | LruCache(const unsigned cache_size, | |
510 | const Key &empty_key, | ||
511 | uint32_t (*hasher)(const Key &key), | ||
512 | perf::StatisticsTemplate statistics) : | ||
513 |
1/2✓ Branch 1 taken 129 times.
✗ Branch 2 not taken.
|
184 | counters_(statistics), |
514 | 184 | pause_(false), | |
515 | 184 | cache_gauge_(0), | |
516 | 184 | cache_size_(cache_size), | |
517 | 184 | allocator_(cache_size), | |
518 |
2/4✓ Branch 3 taken 129 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 129 times.
✗ Branch 7 not taken.
|
368 | lru_list_(&allocator_) |
519 | { | ||
520 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
|
184 | assert(cache_size > 0); |
521 | |||
522 | 184 | counters_.sz_size->Set(cache_size_); | |
523 | 184 | filter_entry_ = NULL; | |
524 | // cache_ = Cache(cache_size_); | ||
525 |
1/2✓ Branch 1 taken 129 times.
✗ Branch 2 not taken.
|
184 | cache_.Init(cache_size_, empty_key, hasher); |
526 | 368 | perf::Xadd(counters_.sz_allocated, allocator_.bytes_allocated() + | |
527 | 184 | cache_.bytes_allocated()); | |
528 | |||
529 | #ifdef LRU_CACHE_THREAD_SAFE | ||
530 | 184 | int retval = pthread_mutex_init(&lock_, NULL); | |
531 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 129 times.
|
184 | assert(retval == 0); |
532 | #endif | ||
533 | 184 | } | |
534 | |||
535 | 96 | static double GetEntrySize() { | |
536 | 96 | return SmallHashFixed<Key, CacheEntry>::GetEntrySize() + | |
537 | 96 | ConcreteMemoryAllocator::GetEntrySize(); | |
538 | } | ||
539 | |||
540 | 258 | virtual ~LruCache() { | |
541 | #ifdef LRU_CACHE_THREAD_SAFE | ||
542 | 258 | pthread_mutex_destroy(&lock_); | |
543 | #endif | ||
544 | } | ||
545 | |||
546 | /** | ||
547 | * Insert a new key-value pair to the list. | ||
548 | * If the cache is already full, the least recently used object is removed; | ||
549 | * afterwards the new object is inserted. | ||
550 | * If the object is already present it is updated and moved back to the end | ||
551 | * of the list | ||
552 | * @param key the key where the value is saved | ||
553 | * @param value the value of the cache entry | ||
554 | * @return true on insert, false on update | ||
555 | */ | ||
556 | 26417 | virtual bool Insert(const Key &key, const Value &value) { | |
557 | 26417 | this->Lock(); | |
558 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26398 times.
|
26417 | if (pause_) { |
559 | 2 | Unlock(); | |
560 | 2 | return false; | |
561 | } | ||
562 | |||
563 |
1/2✓ Branch 1 taken 22244 times.
✗ Branch 2 not taken.
|
26415 | CacheEntry entry; |
564 | |||
565 | // Check if we have to update an existent entry | ||
566 |
3/4✓ Branch 1 taken 26398 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 22096 times.
✓ Branch 4 taken 4302 times.
|
26415 | if (this->DoLookup(key, &entry)) { |
567 | 22096 | perf::Inc(counters_.n_update); | |
568 |
1/2✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
|
22096 | entry.value = value; |
569 |
1/2✓ Branch 1 taken 22096 times.
✗ Branch 2 not taken.
|
22096 | cache_.Insert(key, entry); |
570 |
1/2✓ Branch 1 taken 22096 times.
✗ Branch 2 not taken.
|
22096 | this->Touch(entry); |
571 | 22096 | this->Unlock(); | |
572 | 22096 | return false; | |
573 | } | ||
574 | |||
575 | 4319 | perf::Inc(counters_.n_insert); | |
576 | // Check if we have to make some space in the cache a | ||
577 |
2/2✓ Branch 1 taken 1019 times.
✓ Branch 2 taken 3283 times.
|
4319 | if (this->IsFull()) |
578 |
1/2✓ Branch 1 taken 1019 times.
✗ Branch 2 not taken.
|
1019 | this->DeleteOldest(); |
579 | |||
580 |
1/2✓ Branch 1 taken 4302 times.
✗ Branch 2 not taken.
|
4319 | entry.list_entry = lru_list_.PushBack(key); |
581 |
1/2✓ Branch 1 taken 4162 times.
✗ Branch 2 not taken.
|
4319 | entry.value = value; |
582 | |||
583 |
1/2✓ Branch 1 taken 4302 times.
✗ Branch 2 not taken.
|
4319 | cache_.Insert(key, entry); |
584 | 4319 | cache_gauge_++; | |
585 | |||
586 | 4319 | Unlock(); | |
587 | 4319 | return true; | |
588 | 4188 | } | |
589 | |||
590 | |||
591 | /** | ||
592 | * Updates object and moves back to the end of the list. The object must be | ||
593 | * present. | ||
594 | */ | ||
595 | 1 | virtual void Update(const Key &key) { | |
596 | 1 | Lock(); | |
597 | // Is not called from the client, only from the cache plugin | ||
598 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | assert(!pause_); |
599 |
0/2✗ Branch 1 not taken.
✗ Branch 2 not taken.
|
1 | CacheEntry entry; |
600 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | bool retval = DoLookup(key, &entry); |
601 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | assert(retval); |
602 | 1 | perf::Inc(counters_.n_update); | |
603 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | Touch(entry); |
604 | 1 | Unlock(); | |
605 | 1 | } | |
606 | |||
607 | |||
608 | /** | ||
609 | * Changes the value of an entry in the LRU cache without updating the LRU | ||
610 | * order. | ||
611 | */ | ||
612 | 3 | virtual bool UpdateValue(const Key &key, const Value &value) { | |
613 | 3 | this->Lock(); | |
614 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (pause_) { |
615 | ✗ | Unlock(); | |
616 | ✗ | return false; | |
617 | } | ||
618 | |||
619 |
0/2✗ Branch 1 not taken.
✗ Branch 2 not taken.
|
3 | CacheEntry entry; |
620 |
3/4✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
|
3 | if (!this->DoLookup(key, &entry)) { |
621 | 1 | this->Unlock(); | |
622 | 1 | return false; | |
623 | } | ||
624 | |||
625 | 2 | perf::Inc(counters_.n_update_value); | |
626 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | entry.value = value; |
627 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
2 | cache_.Insert(key, entry); |
628 | 2 | this->Unlock(); | |
629 | 2 | return true; | |
630 | 3 | } | |
631 | |||
632 | |||
633 | /** | ||
634 | * Retrieve an element from the cache. | ||
635 | * If the element was found, it will be marked as 'recently used' and returned | ||
636 | * @param key the key to perform a lookup on | ||
637 | * @param value (out) here the result is saved (not touch in case of miss) | ||
638 | * @return true on successful lookup, false if key was not found | ||
639 | */ | ||
640 | 24534 | virtual bool Lookup(const Key &key, Value *value, bool update_lru = true) { | |
641 | 24534 | bool found = false; | |
642 | 24534 | Lock(); | |
643 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 24485 times.
|
24534 | if (pause_) { |
644 | 20 | Unlock(); | |
645 | 20 | return false; | |
646 | } | ||
647 | |||
648 |
1/2✓ Branch 1 taken 22332 times.
✗ Branch 2 not taken.
|
24514 | CacheEntry entry; |
649 |
3/4✓ Branch 1 taken 24485 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 24250 times.
✓ Branch 4 taken 235 times.
|
24514 | if (DoLookup(key, &entry)) { |
650 | // Hit | ||
651 | 24262 | perf::Inc(counters_.n_hit); | |
652 |
2/2✓ Branch 0 taken 24219 times.
✓ Branch 1 taken 31 times.
|
24262 | if (update_lru) |
653 |
1/2✓ Branch 1 taken 24219 times.
✗ Branch 2 not taken.
|
24231 | Touch(entry); |
654 |
1/2✓ Branch 1 taken 2122 times.
✗ Branch 2 not taken.
|
24262 | *value = entry.value; |
655 | 24262 | found = true; | |
656 | } else { | ||
657 | 252 | perf::Inc(counters_.n_miss); | |
658 | } | ||
659 | |||
660 | 24514 | Unlock(); | |
661 | 24514 | return found; | |
662 | 2211 | } | |
663 | |||
664 | /** | ||
665 | * Forgets about a specific cache entry | ||
666 | * @param key the key to delete from the cache | ||
667 | * @return true if key was deleted, false if key was not in the cache | ||
668 | */ | ||
669 | 24 | virtual bool Forget(const Key &key) { | |
670 | 24 | bool found = false; | |
671 | 24 | this->Lock(); | |
672 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 22 times.
|
24 | if (pause_) { |
673 | 2 | Unlock(); | |
674 | 2 | return false; | |
675 | } | ||
676 | |||
677 |
1/2✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
|
22 | CacheEntry entry; |
678 |
3/4✓ Branch 1 taken 22 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 7 times.
|
22 | if (this->DoLookup(key, &entry)) { |
679 | 15 | found = true; | |
680 | 15 | perf::Inc(counters_.n_forget); | |
681 | |||
682 |
1/2✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
|
15 | entry.list_entry->RemoveFromList(); |
683 |
1/2✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
|
15 | allocator_.Destruct(entry.list_entry); |
684 |
1/2✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
|
15 | cache_.Erase(key); |
685 | 15 | --cache_gauge_; | |
686 | } | ||
687 | |||
688 | 22 | this->Unlock(); | |
689 | 22 | return found; | |
690 | 18 | } | |
691 | |||
692 | /** | ||
693 | * Clears all elements from the cache. | ||
694 | * All memory of internal data structures will be freed but data of | ||
695 | * cache entries may stay in use, we do not call delete on any user data. | ||
696 | */ | ||
697 | 2 | virtual void Drop() { | |
698 | 2 | this->Lock(); | |
699 | |||
700 | 2 | cache_gauge_ = 0; | |
701 | 2 | lru_list_.clear(); | |
702 | 2 | cache_.Clear(); | |
703 | 2 | perf::Inc(counters_.n_drop); | |
704 | 2 | counters_.sz_allocated->Set(0); | |
705 | 4 | perf::Xadd(counters_.sz_allocated, allocator_.bytes_allocated() + | |
706 | 2 | cache_.bytes_allocated()); | |
707 | |||
708 | 2 | this->Unlock(); | |
709 | 2 | } | |
710 | |||
711 | 6 | void Pause() { | |
712 | 6 | Lock(); | |
713 | 6 | pause_ = true; | |
714 | 6 | Unlock(); | |
715 | 6 | } | |
716 | |||
717 | 3 | void Resume() { | |
718 | 3 | Lock(); | |
719 | 3 | pause_ = false; | |
720 | 3 | Unlock(); | |
721 | 3 | } | |
722 | |||
723 | 9473 | inline bool IsFull() const { return cache_gauge_ >= cache_size_; } | |
724 | 1059 | inline bool IsEmpty() const { return cache_gauge_ == 0; } | |
725 | |||
726 | Counters counters() { | ||
727 | Lock(); | ||
728 | cache_.GetCollisionStats(&counters_.num_collisions, | ||
729 | &counters_.max_collisions); | ||
730 | Unlock(); | ||
731 | return counters_; | ||
732 | } | ||
733 | |||
734 | /** | ||
735 | * Prepares for in-order iteration of the cache entries to perform a filter | ||
736 | * operation. To ensure consistency, the LruCache must be locked for the | ||
737 | * duration of the filter operation. | ||
738 | */ | ||
739 | 12 | virtual void FilterBegin() { | |
740 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | assert(!filter_entry_); |
741 | 12 | Lock(); | |
742 | 12 | filter_entry_ = &lru_list_; | |
743 | 12 | } | |
744 | |||
745 | /** | ||
746 | * Get the current key and value for the filter operation | ||
747 | * @param key Address to write the key | ||
748 | * @param value Address to write the value | ||
749 | */ | ||
750 | 116 | virtual void FilterGet(Key *key, Value *value) { | |
751 |
1/2✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
|
116 | CacheEntry entry; |
752 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
|
116 | assert(filter_entry_); |
753 |
2/4✓ Branch 1 taken 116 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 116 times.
|
116 | assert(!filter_entry_->IsListHead()); |
754 | 116 | *key = static_cast<ConcreteListEntryContent *>(filter_entry_)->content(); | |
755 |
1/2✓ Branch 1 taken 116 times.
✗ Branch 2 not taken.
|
116 | bool rc = this->DoLookup(*key, &entry); |
756 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
|
116 | assert(rc); |
757 |
1/2✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
|
116 | *value = entry.value; |
758 | 116 | } | |
759 | |||
760 | /** | ||
761 | * Advance to the next entry in the list | ||
762 | * @returns false upon reaching the end of the cache list | ||
763 | */ | ||
764 | 126 | virtual bool FilterNext() { | |
765 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
|
126 | assert(filter_entry_); |
766 | 126 | filter_entry_ = filter_entry_->next; | |
767 | 126 | return !filter_entry_->IsListHead(); | |
768 | } | ||
769 | |||
770 | /** | ||
771 | * Delete the current cache list entry | ||
772 | */ | ||
773 | 104 | virtual void FilterDelete() { | |
774 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
|
104 | assert(filter_entry_); |
775 |
2/4✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 104 times.
|
104 | assert(!filter_entry_->IsListHead()); |
776 | 104 | ListEntry<Key> *new_current = filter_entry_->prev; | |
777 | 104 | perf::Inc(counters_.n_forget); | |
778 | 104 | Key k = static_cast<ConcreteListEntryContent *>(filter_entry_)->content(); | |
779 |
1/2✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
|
104 | filter_entry_->RemoveFromList(); |
780 |
1/2✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
|
104 | allocator_.Destruct(static_cast<ConcreteListEntryContent *>(filter_entry_)); |
781 |
1/2✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
|
104 | cache_.Erase(k); |
782 | 104 | --cache_gauge_; | |
783 | 104 | filter_entry_ = new_current; | |
784 | 104 | } | |
785 | |||
786 | /** | ||
787 | * Finish filtering the entries and unlock the cache | ||
788 | */ | ||
789 | 12 | virtual void FilterEnd() { | |
790 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
|
12 | assert(filter_entry_); |
791 | 12 | filter_entry_ = NULL; | |
792 | 12 | Unlock(); | |
793 | 12 | } | |
794 | |||
795 | protected: | ||
796 | Counters counters_; | ||
797 | |||
798 | private: | ||
799 | /** | ||
800 | * this just performs a lookup in the cache | ||
801 | * WITHOUT changing the LRU order | ||
802 | * @param key the key to perform a lookup on | ||
803 | * @param entry a pointer to the entry structure | ||
804 | * @return true on successful lookup, false otherwise | ||
805 | */ | ||
806 | 51071 | inline bool DoLookup(const Key &key, CacheEntry *entry) { | |
807 | 51071 | return cache_.Lookup(key, entry); | |
808 | } | ||
809 | |||
810 | /** | ||
811 | * Touch an entry. | ||
812 | * The entry will be moved to the back of the LRU list to mark it | ||
813 | * as 'recently used'... this saves the entry from being deleted | ||
814 | * @param entry the CacheEntry to be touched (CacheEntry is the internal wrapper data structure) | ||
815 | */ | ||
816 | 46328 | inline void Touch(const CacheEntry &entry) { | |
817 | 46328 | lru_list_.MoveToBack(entry.list_entry); | |
818 | 46328 | } | |
819 | |||
820 | /** | ||
821 | * Deletes the least recently used entry from the cache. | ||
822 | */ | ||
823 | 1019 | inline void DeleteOldest() { | |
824 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1019 times.
|
1019 | assert(!this->IsEmpty()); |
825 | |||
826 | 1019 | perf::Inc(counters_.n_replace); | |
827 |
1/2✓ Branch 1 taken 1019 times.
✗ Branch 2 not taken.
|
1019 | Key delete_me = lru_list_.PopFront(); |
828 |
1/2✓ Branch 1 taken 1019 times.
✗ Branch 2 not taken.
|
1019 | cache_.Erase(delete_me); |
829 | |||
830 | 1019 | --cache_gauge_; | |
831 | 1019 | } | |
832 | |||
833 | /** | ||
834 | * Locks the cache (thread safety). | ||
835 | */ | ||
836 | 51002 | inline void Lock() { | |
837 | #ifdef LRU_CACHE_THREAD_SAFE | ||
838 | 51002 | pthread_mutex_lock(&lock_); | |
839 | #endif | ||
840 | 51002 | } | |
841 | |||
842 | /** | ||
843 | * Unlocks the cache (thread safety). | ||
844 | */ | ||
845 | 51002 | inline void Unlock() { | |
846 | #ifdef LRU_CACHE_THREAD_SAFE | ||
847 | 51002 | pthread_mutex_unlock(&lock_); | |
848 | #endif | ||
849 | 51002 | } | |
850 | |||
851 | bool pause_; /**< Temporarily stops the cache in order to avoid poisoning */ | ||
852 | |||
853 | // Internal data fields | ||
854 | unsigned int cache_gauge_; | ||
855 | const unsigned int cache_size_; | ||
856 | ConcreteMemoryAllocator allocator_; | ||
857 | |||
858 | /** | ||
859 | * A doubly linked list to keep track of the least recently used data entries. | ||
860 | * New entries get pushed back to the list. If an entry is touched | ||
861 | * it is moved to the back of the list again. | ||
862 | * If the cache gets too long, the first element (the oldest) gets | ||
863 | * deleted to obtain some space. | ||
864 | */ | ||
865 | ListEntryHead<Key> lru_list_; | ||
866 | SmallHashFixed<Key, CacheEntry> cache_; | ||
867 | |||
868 | ListEntry<Key> *filter_entry_; | ||
869 | #ifdef LRU_CACHE_THREAD_SAFE | ||
870 | pthread_mutex_t lock_; /**< Mutex to make cache thread safe. */ | ||
871 | #endif | ||
872 | }; // class LruCache | ||
873 | |||
874 | } // namespace lru | ||
875 | |||
876 | #endif // CVMFS_LRU_H_ | ||
877 |