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