/src/leveldb/util/cache.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. |
2 | | // Use of this source code is governed by a BSD-style license that can be |
3 | | // found in the LICENSE file. See the AUTHORS file for names of contributors. |
4 | | |
5 | | #include "leveldb/cache.h" |
6 | | |
7 | | #include <cassert> |
8 | | #include <cstdio> |
9 | | #include <cstdlib> |
10 | | |
11 | | #include "port/port.h" |
12 | | #include "port/thread_annotations.h" |
13 | | #include "util/hash.h" |
14 | | #include "util/mutexlock.h" |
15 | | |
16 | | namespace leveldb { |
17 | | |
18 | 237k | Cache::~Cache() {} |
19 | | |
20 | | namespace { |
21 | | |
22 | | // LRU cache implementation |
23 | | // |
24 | | // Cache entries have an "in_cache" boolean indicating whether the cache has a |
25 | | // reference on the entry. The only ways that this can become false without the |
26 | | // entry being passed to its "deleter" are via Erase(), via Insert() when |
27 | | // an element with a duplicate key is inserted, or on destruction of the cache. |
28 | | // |
29 | | // The cache keeps two linked lists of items in the cache. All items in the |
30 | | // cache are in one list or the other, and never both. Items still referenced |
31 | | // by clients but erased from the cache are in neither list. The lists are: |
32 | | // - in-use: contains the items currently referenced by clients, in no |
33 | | // particular order. (This list is used for invariant checking. If we |
34 | | // removed the check, elements that would otherwise be on this list could be |
35 | | // left as disconnected singleton lists.) |
36 | | // - LRU: contains the items not currently referenced by clients, in LRU order |
37 | | // Elements are moved between these lists by the Ref() and Unref() methods, |
38 | | // when they detect an element in the cache acquiring or losing its only |
39 | | // external reference. |
40 | | |
41 | | // An entry is a variable length heap-allocated structure. Entries |
42 | | // are kept in a circular doubly linked list ordered by access time. |
43 | | struct LRUHandle { |
44 | | void* value; |
45 | | void (*deleter)(const Slice&, void* value); |
46 | | LRUHandle* next_hash; |
47 | | LRUHandle* next; |
48 | | LRUHandle* prev; |
49 | | size_t charge; // TODO(opt): Only allow uint32_t? |
50 | | size_t key_length; |
51 | | bool in_cache; // Whether entry is in the cache. |
52 | | uint32_t refs; // References, including cache reference, if present. |
53 | | uint32_t hash; // Hash of key(); used for fast sharding and comparisons |
54 | | char key_data[1]; // Beginning of key |
55 | | |
56 | 1.58M | Slice key() const { |
57 | | // next is only equal to this if the LRU handle is the list head of an |
58 | | // empty list. List heads never have meaningful keys. |
59 | 1.58M | assert(next != this); |
60 | | |
61 | 1.58M | return Slice(key_data, key_length); |
62 | 1.58M | } |
63 | | }; |
64 | | |
65 | | // We provide our own simple hash table since it removes a whole bunch |
66 | | // of porting hacks and is also faster than some of the built-in hash |
67 | | // table implementations in some of the compiler/runtime combinations |
68 | | // we have tested. E.g., readrandom speeds up by ~5% over the g++ |
69 | | // 4.4.3's builtin hashtable. |
70 | | class HandleTable { |
71 | | public: |
72 | 3.79M | HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); } |
73 | 3.79M | ~HandleTable() { delete[] list_; } |
74 | | |
75 | 1.62M | LRUHandle* Lookup(const Slice& key, uint32_t hash) { |
76 | 1.62M | return *FindPointer(key, hash); |
77 | 1.62M | } |
78 | | |
79 | 608k | LRUHandle* Insert(LRUHandle* h) { |
80 | 608k | LRUHandle** ptr = FindPointer(h->key(), h->hash); |
81 | 608k | LRUHandle* old = *ptr; |
82 | 608k | h->next_hash = (old == nullptr ? nullptr : old->next_hash); |
83 | 608k | *ptr = h; |
84 | 608k | if (old == nullptr) { |
85 | 607k | ++elems_; |
86 | 607k | if (elems_ > length_) { |
87 | | // Since each cache entry is fairly large, we aim for a small |
88 | | // average linked list length (<= 1). |
89 | 21.8k | Resize(); |
90 | 21.8k | } |
91 | 607k | } |
92 | 608k | return old; |
93 | 608k | } |
94 | | |
95 | 68.0k | LRUHandle* Remove(const Slice& key, uint32_t hash) { |
96 | 68.0k | LRUHandle** ptr = FindPointer(key, hash); |
97 | 68.0k | LRUHandle* result = *ptr; |
98 | 68.0k | if (result != nullptr) { |
99 | 61.5k | *ptr = result->next_hash; |
100 | 61.5k | --elems_; |
101 | 61.5k | } |
102 | 68.0k | return result; |
103 | 68.0k | } |
104 | | |
105 | | private: |
106 | | // The table consists of an array of buckets where each bucket is |
107 | | // a linked list of cache entries that hash into the bucket. |
108 | | uint32_t length_; |
109 | | uint32_t elems_; |
110 | | LRUHandle** list_; |
111 | | |
112 | | // Return a pointer to slot that points to a cache entry that |
113 | | // matches key/hash. If there is no such cache entry, return a |
114 | | // pointer to the trailing slot in the corresponding linked list. |
115 | 2.29M | LRUHandle** FindPointer(const Slice& key, uint32_t hash) { |
116 | 2.29M | LRUHandle** ptr = &list_[hash & (length_ - 1)]; |
117 | 2.55M | while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) { |
118 | 251k | ptr = &(*ptr)->next_hash; |
119 | 251k | } |
120 | 2.29M | return ptr; |
121 | 2.29M | } |
122 | | |
123 | 3.81M | void Resize() { |
124 | 3.81M | uint32_t new_length = 4; |
125 | 3.84M | while (new_length < elems_) { |
126 | 24.6k | new_length *= 2; |
127 | 24.6k | } |
128 | 3.81M | LRUHandle** new_list = new LRUHandle*[new_length]; |
129 | 3.81M | memset(new_list, 0, sizeof(new_list[0]) * new_length); |
130 | 3.81M | uint32_t count = 0; |
131 | 3.91M | for (uint32_t i = 0; i < length_; i++) { |
132 | 98.7k | LRUHandle* h = list_[i]; |
133 | 219k | while (h != nullptr) { |
134 | 120k | LRUHandle* next = h->next_hash; |
135 | 120k | uint32_t hash = h->hash; |
136 | 120k | LRUHandle** ptr = &new_list[hash & (new_length - 1)]; |
137 | 120k | h->next_hash = *ptr; |
138 | 120k | *ptr = h; |
139 | 120k | h = next; |
140 | 120k | count++; |
141 | 120k | } |
142 | 98.7k | } |
143 | 3.81M | assert(elems_ == count); |
144 | 3.81M | delete[] list_; |
145 | 3.81M | list_ = new_list; |
146 | 3.81M | length_ = new_length; |
147 | 3.81M | } |
148 | | }; |
149 | | |
150 | | // A single shard of sharded cache. |
151 | | class LRUCache { |
152 | | public: |
153 | | LRUCache(); |
154 | | ~LRUCache(); |
155 | | |
156 | | // Separate from constructor so caller can easily make an array of LRUCache |
157 | 3.79M | void SetCapacity(size_t capacity) { capacity_ = capacity; } |
158 | | |
159 | | // Like Cache methods, but with an extra "hash" parameter. |
160 | | Cache::Handle* Insert(const Slice& key, uint32_t hash, void* value, |
161 | | size_t charge, |
162 | | void (*deleter)(const Slice& key, void* value)); |
163 | | Cache::Handle* Lookup(const Slice& key, uint32_t hash); |
164 | | void Release(Cache::Handle* handle); |
165 | | void Erase(const Slice& key, uint32_t hash); |
166 | | void Prune(); |
167 | 288 | size_t TotalCharge() const { |
168 | 288 | MutexLock l(&mutex_); |
169 | 288 | return usage_; |
170 | 288 | } |
171 | | |
172 | | private: |
173 | | void LRU_Remove(LRUHandle* e); |
174 | | void LRU_Append(LRUHandle* list, LRUHandle* e); |
175 | | void Ref(LRUHandle* e); |
176 | | void Unref(LRUHandle* e); |
177 | | bool FinishErase(LRUHandle* e) EXCLUSIVE_LOCKS_REQUIRED(mutex_); |
178 | | |
179 | | // Initialized before use. |
180 | | size_t capacity_; |
181 | | |
182 | | // mutex_ protects the following state. |
183 | | mutable port::Mutex mutex_; |
184 | | size_t usage_ GUARDED_BY(mutex_); |
185 | | |
186 | | // Dummy head of LRU list. |
187 | | // lru.prev is newest entry, lru.next is oldest entry. |
188 | | // Entries have refs==1 and in_cache==true. |
189 | | LRUHandle lru_ GUARDED_BY(mutex_); |
190 | | |
191 | | // Dummy head of in-use list. |
192 | | // Entries are in use by clients, and have refs >= 2 and in_cache==true. |
193 | | LRUHandle in_use_ GUARDED_BY(mutex_); |
194 | | |
195 | | HandleTable table_ GUARDED_BY(mutex_); |
196 | | }; |
197 | | |
198 | 3.79M | LRUCache::LRUCache() : capacity_(0), usage_(0) { |
199 | | // Make empty circular linked lists. |
200 | 3.79M | lru_.next = &lru_; |
201 | 3.79M | lru_.prev = &lru_; |
202 | 3.79M | in_use_.next = &in_use_; |
203 | 3.79M | in_use_.prev = &in_use_; |
204 | 3.79M | } |
205 | | |
206 | 3.79M | LRUCache::~LRUCache() { |
207 | 3.79M | assert(in_use_.next == &in_use_); // Error if caller has an unreleased handle |
208 | 4.34M | for (LRUHandle* e = lru_.next; e != &lru_;) { |
209 | 545k | LRUHandle* next = e->next; |
210 | 545k | assert(e->in_cache); |
211 | 545k | e->in_cache = false; |
212 | 545k | assert(e->refs == 1); // Invariant of lru_ list. |
213 | 545k | Unref(e); |
214 | 545k | e = next; |
215 | 545k | } |
216 | 3.79M | } |
217 | | |
218 | 305k | void LRUCache::Ref(LRUHandle* e) { |
219 | 305k | if (e->refs == 1 && e->in_cache) { // If on lru_ list, move to in_use_ list. |
220 | 219k | LRU_Remove(e); |
221 | 219k | LRU_Append(&in_use_, e); |
222 | 219k | } |
223 | 305k | e->refs++; |
224 | 305k | } |
225 | | |
226 | 1.52M | void LRUCache::Unref(LRUHandle* e) { |
227 | 1.52M | assert(e->refs > 0); |
228 | 1.52M | e->refs--; |
229 | 1.52M | if (e->refs == 0) { // Deallocate. |
230 | 608k | assert(!e->in_cache); |
231 | 608k | (*e->deleter)(e->key(), e->value); |
232 | 608k | free(e); |
233 | 914k | } else if (e->in_cache && e->refs == 1) { |
234 | | // No longer in use; move to lru_ list. |
235 | 827k | LRU_Remove(e); |
236 | 827k | LRU_Append(&lru_, e); |
237 | 827k | } |
238 | 1.52M | } |
239 | | |
240 | 1.10M | void LRUCache::LRU_Remove(LRUHandle* e) { |
241 | 1.10M | e->next->prev = e->prev; |
242 | 1.10M | e->prev->next = e->next; |
243 | 1.10M | } |
244 | | |
245 | 1.65M | void LRUCache::LRU_Append(LRUHandle* list, LRUHandle* e) { |
246 | | // Make "e" newest entry by inserting just before *list |
247 | 1.65M | e->next = list; |
248 | 1.65M | e->prev = list->prev; |
249 | 1.65M | e->prev->next = e; |
250 | 1.65M | e->next->prev = e; |
251 | 1.65M | } |
252 | | |
253 | 1.62M | Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) { |
254 | 1.62M | MutexLock l(&mutex_); |
255 | 1.62M | LRUHandle* e = table_.Lookup(key, hash); |
256 | 1.62M | if (e != nullptr) { |
257 | 305k | Ref(e); |
258 | 305k | } |
259 | 1.62M | return reinterpret_cast<Cache::Handle*>(e); |
260 | 1.62M | } |
261 | | |
262 | 914k | void LRUCache::Release(Cache::Handle* handle) { |
263 | 914k | MutexLock l(&mutex_); |
264 | 914k | Unref(reinterpret_cast<LRUHandle*>(handle)); |
265 | 914k | } |
266 | | |
267 | | Cache::Handle* LRUCache::Insert(const Slice& key, uint32_t hash, void* value, |
268 | | size_t charge, |
269 | | void (*deleter)(const Slice& key, |
270 | 608k | void* value)) { |
271 | 608k | MutexLock l(&mutex_); |
272 | | |
273 | 608k | LRUHandle* e = |
274 | 608k | reinterpret_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size())); |
275 | 608k | e->value = value; |
276 | 608k | e->deleter = deleter; |
277 | 608k | e->charge = charge; |
278 | 608k | e->key_length = key.size(); |
279 | 608k | e->hash = hash; |
280 | 608k | e->in_cache = false; |
281 | 608k | e->refs = 1; // for the returned handle. |
282 | 608k | std::memcpy(e->key_data, key.data(), key.size()); |
283 | | |
284 | 608k | if (capacity_ > 0) { |
285 | 608k | e->refs++; // for the cache's reference. |
286 | 608k | e->in_cache = true; |
287 | 608k | LRU_Append(&in_use_, e); |
288 | 608k | usage_ += charge; |
289 | 608k | FinishErase(table_.Insert(e)); |
290 | 18.4E | } else { // don't cache. (capacity_==0 is supported and turns off caching.) |
291 | | // next is read by key() in an assert, so it must be initialized |
292 | 18.4E | e->next = nullptr; |
293 | 18.4E | } |
294 | 608k | while (usage_ > capacity_ && lru_.next != &lru_) { |
295 | 0 | LRUHandle* old = lru_.next; |
296 | 0 | assert(old->refs == 1); |
297 | 0 | bool erased = FinishErase(table_.Remove(old->key(), old->hash)); |
298 | 0 | if (!erased) { // to avoid unused variable when compiled NDEBUG |
299 | 0 | assert(erased); |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | 608k | return reinterpret_cast<Cache::Handle*>(e); |
304 | 608k | } |
305 | | |
306 | | // If e != nullptr, finish removing *e from the cache; it has already been |
307 | | // removed from the hash table. Return whether e != nullptr. |
308 | 676k | bool LRUCache::FinishErase(LRUHandle* e) { |
309 | 676k | if (e != nullptr) { |
310 | 63.0k | assert(e->in_cache); |
311 | 63.0k | LRU_Remove(e); |
312 | 63.0k | e->in_cache = false; |
313 | 63.0k | usage_ -= e->charge; |
314 | 63.0k | Unref(e); |
315 | 63.0k | } |
316 | 676k | return e != nullptr; |
317 | 676k | } |
318 | | |
319 | 68.0k | void LRUCache::Erase(const Slice& key, uint32_t hash) { |
320 | 68.0k | MutexLock l(&mutex_); |
321 | 68.0k | FinishErase(table_.Remove(key, hash)); |
322 | 68.0k | } |
323 | | |
324 | 0 | void LRUCache::Prune() { |
325 | 0 | MutexLock l(&mutex_); |
326 | 0 | while (lru_.next != &lru_) { |
327 | 0 | LRUHandle* e = lru_.next; |
328 | 0 | assert(e->refs == 1); |
329 | 0 | bool erased = FinishErase(table_.Remove(e->key(), e->hash)); |
330 | 0 | if (!erased) { // to avoid unused variable when compiled NDEBUG |
331 | 0 | assert(erased); |
332 | 0 | } |
333 | 0 | } |
334 | 0 | } |
335 | | |
336 | | static const int kNumShardBits = 4; |
337 | | static const int kNumShards = 1 << kNumShardBits; |
338 | | |
339 | | class ShardedLRUCache : public Cache { |
340 | | private: |
341 | | LRUCache shard_[kNumShards]; |
342 | | port::Mutex id_mutex_; |
343 | | uint64_t last_id_; |
344 | | |
345 | 2.29M | static inline uint32_t HashSlice(const Slice& s) { |
346 | 2.29M | return Hash(s.data(), s.size(), 0); |
347 | 2.29M | } |
348 | | |
349 | 3.21M | static uint32_t Shard(uint32_t hash) { return hash >> (32 - kNumShardBits); } |
350 | | |
351 | | public: |
352 | 237k | explicit ShardedLRUCache(size_t capacity) : last_id_(0) { |
353 | 237k | const size_t per_shard = (capacity + (kNumShards - 1)) / kNumShards; |
354 | 4.03M | for (int s = 0; s < kNumShards; s++) { |
355 | 3.79M | shard_[s].SetCapacity(per_shard); |
356 | 3.79M | } |
357 | 237k | } |
358 | 237k | ~ShardedLRUCache() override {} |
359 | | Handle* Insert(const Slice& key, void* value, size_t charge, |
360 | 608k | void (*deleter)(const Slice& key, void* value)) override { |
361 | 608k | const uint32_t hash = HashSlice(key); |
362 | 608k | return shard_[Shard(hash)].Insert(key, hash, value, charge, deleter); |
363 | 608k | } |
364 | 1.62M | Handle* Lookup(const Slice& key) override { |
365 | 1.62M | const uint32_t hash = HashSlice(key); |
366 | 1.62M | return shard_[Shard(hash)].Lookup(key, hash); |
367 | 1.62M | } |
368 | 914k | void Release(Handle* handle) override { |
369 | 914k | LRUHandle* h = reinterpret_cast<LRUHandle*>(handle); |
370 | 914k | shard_[Shard(h->hash)].Release(handle); |
371 | 914k | } |
372 | 68.0k | void Erase(const Slice& key) override { |
373 | 68.0k | const uint32_t hash = HashSlice(key); |
374 | 68.0k | shard_[Shard(hash)].Erase(key, hash); |
375 | 68.0k | } |
376 | 914k | void* Value(Handle* handle) override { |
377 | 914k | return reinterpret_cast<LRUHandle*>(handle)->value; |
378 | 914k | } |
379 | 608k | uint64_t NewId() override { |
380 | 608k | MutexLock l(&id_mutex_); |
381 | 608k | return ++(last_id_); |
382 | 608k | } |
383 | 0 | void Prune() override { |
384 | 0 | for (int s = 0; s < kNumShards; s++) { |
385 | 0 | shard_[s].Prune(); |
386 | 0 | } |
387 | 0 | } |
388 | 18 | size_t TotalCharge() const override { |
389 | 18 | size_t total = 0; |
390 | 306 | for (int s = 0; s < kNumShards; s++) { |
391 | 288 | total += shard_[s].TotalCharge(); |
392 | 288 | } |
393 | 18 | return total; |
394 | 18 | } |
395 | | }; |
396 | | |
397 | | } // end anonymous namespace |
398 | | |
399 | 237k | Cache* NewLRUCache(size_t capacity) { return new ShardedLRUCache(capacity); } |
400 | | |
401 | | } // namespace leveldb |