Coverage Report

Created: 2026-01-10 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/leveldb/util/cache.cc
Line
Count
Source
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
216k
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.44M
  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.44M
    assert(next != this);
60
61
1.44M
    return Slice(key_data, key_length);
62
1.44M
  }
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.47M
  HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); }
73
3.47M
  ~HandleTable() { delete[] list_; }
74
75
1.47M
  LRUHandle* Lookup(const Slice& key, uint32_t hash) {
76
1.47M
    return *FindPointer(key, hash);
77
1.47M
  }
78
79
551k
  LRUHandle* Insert(LRUHandle* h) {
80
551k
    LRUHandle** ptr = FindPointer(h->key(), h->hash);
81
551k
    LRUHandle* old = *ptr;
82
551k
    h->next_hash = (old == nullptr ? nullptr : old->next_hash);
83
551k
    *ptr = h;
84
551k
    if (old == nullptr) {
85
549k
      ++elems_;
86
549k
      if (elems_ > length_) {
87
        // Since each cache entry is fairly large, we aim for a small
88
        // average linked list length (<= 1).
89
20.5k
        Resize();
90
20.5k
      }
91
549k
    }
92
551k
    return old;
93
551k
  }
94
95
63.4k
  LRUHandle* Remove(const Slice& key, uint32_t hash) {
96
63.4k
    LRUHandle** ptr = FindPointer(key, hash);
97
63.4k
    LRUHandle* result = *ptr;
98
63.4k
    if (result != nullptr) {
99
58.2k
      *ptr = result->next_hash;
100
58.2k
      --elems_;
101
58.2k
    }
102
63.4k
    return result;
103
63.4k
  }
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.08M
  LRUHandle** FindPointer(const Slice& key, uint32_t hash) {
116
2.08M
    LRUHandle** ptr = &list_[hash & (length_ - 1)];
117
2.32M
    while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
118
231k
      ptr = &(*ptr)->next_hash;
119
231k
    }
120
2.08M
    return ptr;
121
2.08M
  }
122
123
3.49M
  void Resize() {
124
3.49M
    uint32_t new_length = 4;
125
3.51M
    while (new_length < elems_) {
126
23.4k
      new_length *= 2;
127
23.4k
    }
128
3.49M
    LRUHandle** new_list = new LRUHandle*[new_length];
129
3.49M
    memset(new_list, 0, sizeof(new_list[0]) * new_length);
130
3.49M
    uint32_t count = 0;
131
3.58M
    for (uint32_t i = 0; i < length_; i++) {
132
93.9k
      LRUHandle* h = list_[i];
133
208k
      while (h != nullptr) {
134
114k
        LRUHandle* next = h->next_hash;
135
114k
        uint32_t hash = h->hash;
136
114k
        LRUHandle** ptr = &new_list[hash & (new_length - 1)];
137
114k
        h->next_hash = *ptr;
138
114k
        *ptr = h;
139
114k
        h = next;
140
114k
        count++;
141
114k
      }
142
93.9k
    }
143
3.49M
    assert(elems_ == count);
144
3.49M
    delete[] list_;
145
3.49M
    list_ = new_list;
146
3.49M
    length_ = new_length;
147
3.49M
  }
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.47M
  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.47M
LRUCache::LRUCache() : capacity_(0), usage_(0) {
199
  // Make empty circular linked lists.
200
3.47M
  lru_.next = &lru_;
201
3.47M
  lru_.prev = &lru_;
202
3.47M
  in_use_.next = &in_use_;
203
3.47M
  in_use_.prev = &in_use_;
204
3.47M
}
205
206
3.47M
LRUCache::~LRUCache() {
207
3.47M
  assert(in_use_.next == &in_use_);  // Error if caller has an unreleased handle
208
3.96M
  for (LRUHandle* e = lru_.next; e != &lru_;) {
209
491k
    LRUHandle* next = e->next;
210
491k
    assert(e->in_cache);
211
491k
    e->in_cache = false;
212
491k
    assert(e->refs == 1);  // Invariant of lru_ list.
213
491k
    Unref(e);
214
491k
    e = next;
215
491k
  }
216
3.47M
}
217
218
278k
void LRUCache::Ref(LRUHandle* e) {
219
278k
  if (e->refs == 1 && e->in_cache) {  // If on lru_ list, move to in_use_ list.
220
207k
    LRU_Remove(e);
221
207k
    LRU_Append(&in_use_, e);
222
207k
  }
223
278k
  e->refs++;
224
278k
}
225
226
1.38M
void LRUCache::Unref(LRUHandle* e) {
227
1.38M
  assert(e->refs > 0);
228
1.38M
  e->refs--;
229
1.38M
  if (e->refs == 0) {  // Deallocate.
230
551k
    assert(!e->in_cache);
231
551k
    (*e->deleter)(e->key(), e->value);
232
551k
    free(e);
233
829k
  } else if (e->in_cache && e->refs == 1) {
234
    // No longer in use; move to lru_ list.
235
757k
    LRU_Remove(e);
236
757k
    LRU_Append(&lru_, e);
237
757k
  }
238
1.38M
}
239
240
1.02M
void LRUCache::LRU_Remove(LRUHandle* e) {
241
1.02M
  e->next->prev = e->prev;
242
1.02M
  e->prev->next = e->next;
243
1.02M
}
244
245
1.51M
void LRUCache::LRU_Append(LRUHandle* list, LRUHandle* e) {
246
  // Make "e" newest entry by inserting just before *list
247
1.51M
  e->next = list;
248
1.51M
  e->prev = list->prev;
249
1.51M
  e->prev->next = e;
250
1.51M
  e->next->prev = e;
251
1.51M
}
252
253
1.47M
Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) {
254
1.47M
  MutexLock l(&mutex_);
255
1.47M
  LRUHandle* e = table_.Lookup(key, hash);
256
1.47M
  if (e != nullptr) {
257
278k
    Ref(e);
258
278k
  }
259
1.47M
  return reinterpret_cast<Cache::Handle*>(e);
260
1.47M
}
261
262
829k
void LRUCache::Release(Cache::Handle* handle) {
263
829k
  MutexLock l(&mutex_);
264
829k
  Unref(reinterpret_cast<LRUHandle*>(handle));
265
829k
}
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
551k
                                                void* value)) {
271
551k
  MutexLock l(&mutex_);
272
273
551k
  LRUHandle* e =
274
551k
      reinterpret_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
275
551k
  e->value = value;
276
551k
  e->deleter = deleter;
277
551k
  e->charge = charge;
278
551k
  e->key_length = key.size();
279
551k
  e->hash = hash;
280
551k
  e->in_cache = false;
281
551k
  e->refs = 1;  // for the returned handle.
282
551k
  std::memcpy(e->key_data, key.data(), key.size());
283
284
551k
  if (capacity_ > 0) {
285
551k
    e->refs++;  // for the cache's reference.
286
551k
    e->in_cache = true;
287
551k
    LRU_Append(&in_use_, e);
288
551k
    usage_ += charge;
289
551k
    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
551k
  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
551k
  return reinterpret_cast<Cache::Handle*>(e);
304
551k
}
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
614k
bool LRUCache::FinishErase(LRUHandle* e) {
309
614k
  if (e != nullptr) {
310
59.5k
    assert(e->in_cache);
311
59.5k
    LRU_Remove(e);
312
59.5k
    e->in_cache = false;
313
59.5k
    usage_ -= e->charge;
314
59.5k
    Unref(e);
315
59.5k
  }
316
614k
  return e != nullptr;
317
614k
}
318
319
63.4k
void LRUCache::Erase(const Slice& key, uint32_t hash) {
320
63.4k
  MutexLock l(&mutex_);
321
63.4k
  FinishErase(table_.Remove(key, hash));
322
63.4k
}
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.08M
  static inline uint32_t HashSlice(const Slice& s) {
346
2.08M
    return Hash(s.data(), s.size(), 0);
347
2.08M
  }
348
349
2.91M
  static uint32_t Shard(uint32_t hash) { return hash >> (32 - kNumShardBits); }
350
351
 public:
352
216k
  explicit ShardedLRUCache(size_t capacity) : last_id_(0) {
353
216k
    const size_t per_shard = (capacity + (kNumShards - 1)) / kNumShards;
354
3.68M
    for (int s = 0; s < kNumShards; s++) {
355
3.47M
      shard_[s].SetCapacity(per_shard);
356
3.47M
    }
357
216k
  }
358
216k
  ~ShardedLRUCache() override {}
359
  Handle* Insert(const Slice& key, void* value, size_t charge,
360
551k
                 void (*deleter)(const Slice& key, void* value)) override {
361
551k
    const uint32_t hash = HashSlice(key);
362
551k
    return shard_[Shard(hash)].Insert(key, hash, value, charge, deleter);
363
551k
  }
364
1.47M
  Handle* Lookup(const Slice& key) override {
365
1.47M
    const uint32_t hash = HashSlice(key);
366
1.47M
    return shard_[Shard(hash)].Lookup(key, hash);
367
1.47M
  }
368
829k
  void Release(Handle* handle) override {
369
829k
    LRUHandle* h = reinterpret_cast<LRUHandle*>(handle);
370
829k
    shard_[Shard(h->hash)].Release(handle);
371
829k
  }
372
63.4k
  void Erase(const Slice& key) override {
373
63.4k
    const uint32_t hash = HashSlice(key);
374
63.4k
    shard_[Shard(hash)].Erase(key, hash);
375
63.4k
  }
376
829k
  void* Value(Handle* handle) override {
377
829k
    return reinterpret_cast<LRUHandle*>(handle)->value;
378
829k
  }
379
551k
  uint64_t NewId() override {
380
551k
    MutexLock l(&id_mutex_);
381
551k
    return ++(last_id_);
382
551k
  }
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
216k
Cache* NewLRUCache(size_t capacity) { return new ShardedLRUCache(capacity); }
400
401
}  // namespace leveldb