1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5 //
6 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
9 #pragma once
10
11 #include <string>
12
13 #include "cache/sharded_cache.h"
14
15 #include "port/malloc.h"
16 #include "port/port.h"
17 #include "util/autovector.h"
18
19 namespace ROCKSDB_NAMESPACE {
20
21 // LRU cache implementation. This class is not thread-safe.
22
23 // An entry is a variable length heap-allocated structure.
24 // Entries are referenced by cache and/or by any external entity.
25 // The cache keeps all its entries in a hash table. Some elements
26 // are also stored on LRU list.
27 //
28 // LRUHandle can be in these states:
29 // 1. Referenced externally AND in hash table.
30 // In that case the entry is *not* in the LRU list
31 // (refs >= 1 && in_cache == true)
32 // 2. Not referenced externally AND in hash table.
33 // In that case the entry is in the LRU list and can be freed.
34 // (refs == 0 && in_cache == true)
35 // 3. Referenced externally AND not in hash table.
36 // In that case the entry is not in the LRU list and not in hash table.
37 // The entry can be freed when refs becomes 0.
38 // (refs >= 1 && in_cache == false)
39 //
40 // All newly created LRUHandles are in state 1. If you call
41 // LRUCacheShard::Release on entry in state 1, it will go into state 2.
42 // To move from state 1 to state 3, either call LRUCacheShard::Erase or
43 // LRUCacheShard::Insert with the same key (but possibly different value).
44 // To move from state 2 to state 1, use LRUCacheShard::Lookup.
45 // Before destruction, make sure that no handles are in state 1. This means
46 // that any successful LRUCacheShard::Lookup/LRUCacheShard::Insert have a
47 // matching LRUCache::Release (to move into state 2) or LRUCacheShard::Erase
48 // (to move into state 3).
49
50 struct LRUHandle {
51 using Deleter = Cache::Deleter;
52
53 void* value;
54 Deleter* deleter;
55 LRUHandle* next_hash;
56 LRUHandle* next;
57 LRUHandle* prev;
58 size_t charge; // TODO(opt): Only allow uint32_t?
59 size_t key_length;
60 // The hash of key(). Used for fast sharding and comparisons.
61 uint32_t hash;
62 // The number of external refs to this entry. The cache itself is not counted.
63 uint32_t refs;
64
65 enum Flags : uint8_t {
66 // Whether this entry is referenced by the hash table.
67 IN_CACHE = (1 << 0),
68 // Whether this entry is high priority entry.
69 IS_HIGH_PRI = (1 << 1),
70 // Whether this entry is in high-pri pool.
71 IN_HIGH_PRI_POOL = (1 << 2),
72 // Wwhether this entry has had any lookups (hits).
73 HAS_HIT = (1 << 3),
74 };
75
76 uint8_t flags;
77
78 // Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
79 char key_data[1];
80
keyLRUHandle81 Slice key() const { return Slice(key_data, key_length); }
82
83 // Increase the reference count by 1.
RefLRUHandle84 void Ref() { refs++; }
85
86 // Just reduce the reference count by 1. Return true if it was last reference.
UnrefLRUHandle87 bool Unref() {
88 assert(refs > 0);
89 refs--;
90 return refs == 0;
91 }
92
93 // Return true if there are external refs, false otherwise.
HasRefsLRUHandle94 bool HasRefs() const { return refs > 0; }
95
InCacheLRUHandle96 bool InCache() const { return flags & IN_CACHE; }
IsHighPriLRUHandle97 bool IsHighPri() const { return flags & IS_HIGH_PRI; }
InHighPriPoolLRUHandle98 bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
HasHitLRUHandle99 bool HasHit() const { return flags & HAS_HIT; }
100
SetInCacheLRUHandle101 void SetInCache(bool in_cache) {
102 if (in_cache) {
103 flags |= IN_CACHE;
104 } else {
105 flags &= ~IN_CACHE;
106 }
107 }
108
SetPriorityLRUHandle109 void SetPriority(Cache::Priority priority) {
110 if (priority == Cache::Priority::HIGH) {
111 flags |= IS_HIGH_PRI;
112 } else {
113 flags &= ~IS_HIGH_PRI;
114 }
115 }
116
SetInHighPriPoolLRUHandle117 void SetInHighPriPool(bool in_high_pri_pool) {
118 if (in_high_pri_pool) {
119 flags |= IN_HIGH_PRI_POOL;
120 } else {
121 flags &= ~IN_HIGH_PRI_POOL;
122 }
123 }
124
SetHitLRUHandle125 void SetHit() { flags |= HAS_HIT; }
126
FreeLRUHandle127 void Free() {
128 assert(refs == 0);
129 if (deleter) {
130 (*deleter)(key(), value);
131 }
132 delete[] reinterpret_cast<char*>(this);
133 }
134
135 // Caclculate the memory usage by metadata
CalcTotalChargeLRUHandle136 inline size_t CalcTotalCharge(
137 CacheMetadataChargePolicy metadata_charge_policy) {
138 size_t meta_charge = 0;
139 if (metadata_charge_policy == kFullChargeCacheMetadata) {
140 #ifdef ROCKSDB_MALLOC_USABLE_SIZE
141 meta_charge += malloc_usable_size(static_cast<void*>(this));
142 #else
143 // This is the size that is used when a new handle is created
144 meta_charge += sizeof(LRUHandle) - 1 + key_length;
145 #endif
146 }
147 return charge + meta_charge;
148 }
149 };
150
151 // We provide our own simple hash table since it removes a whole bunch
152 // of porting hacks and is also faster than some of the built-in hash
153 // table implementations in some of the compiler/runtime combinations
154 // we have tested. E.g., readrandom speeds up by ~5% over the g++
155 // 4.4.3's builtin hashtable.
156 class LRUHandleTable {
157 public:
158 LRUHandleTable();
159 ~LRUHandleTable();
160
161 LRUHandle* Lookup(const Slice& key, uint32_t hash);
162 LRUHandle* Insert(LRUHandle* h);
163 LRUHandle* Remove(const Slice& key, uint32_t hash);
164
165 template <typename T>
ApplyToAllCacheEntries(T func)166 void ApplyToAllCacheEntries(T func) {
167 for (uint32_t i = 0; i < length_; i++) {
168 LRUHandle* h = list_[i];
169 while (h != nullptr) {
170 auto n = h->next_hash;
171 assert(h->InCache());
172 func(h);
173 h = n;
174 }
175 }
176 }
177
178 private:
179 // Return a pointer to slot that points to a cache entry that
180 // matches key/hash. If there is no such cache entry, return a
181 // pointer to the trailing slot in the corresponding linked list.
182 LRUHandle** FindPointer(const Slice& key, uint32_t hash);
183
184 void Resize();
185
186 // The table consists of an array of buckets where each bucket is
187 // a linked list of cache entries that hash into the bucket.
188 LRUHandle** list_;
189 uint32_t length_;
190 uint32_t elems_;
191 };
192
193 // A single shard of sharded cache.
ALIGN_AS(CACHE_LINE_SIZE)194 class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
195 public:
196 LRUCacheShard(size_t capacity, bool strict_capacity_limit,
197 double high_pri_pool_ratio, bool use_adaptive_mutex,
198 CacheMetadataChargePolicy metadata_charge_policy);
199 virtual ~LRUCacheShard() override = default;
200
201 // Separate from constructor so caller can easily make an array of LRUCache
202 // if current usage is more than new capacity, the function will attempt to
203 // free the needed space
204 virtual void SetCapacity(size_t capacity) override;
205
206 // Set the flag to reject insertion if cache if full.
207 virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override;
208
209 // Set percentage of capacity reserved for high-pri cache entries.
210 void SetHighPriorityPoolRatio(double high_pri_pool_ratio);
211
212 // Like Cache methods, but with an extra "hash" parameter.
213 virtual Status Insert(const Slice& key, uint32_t hash, void* value,
214 size_t charge, Deleter* deleter, Cache::Handle** handle,
215 Cache::Priority priority) override;
216 virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
217 virtual bool Ref(Cache::Handle* handle) override;
218 virtual bool Release(Cache::Handle* handle,
219 bool force_erase = false) override;
220 virtual void Erase(const Slice& key, uint32_t hash) override;
221
222 // Although in some platforms the update of size_t is atomic, to make sure
223 // GetUsage() and GetPinnedUsage() work correctly under any platform, we'll
224 // protect them with mutex_.
225
226 virtual size_t GetUsage() const override;
227 virtual size_t GetPinnedUsage() const override;
228
229 virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
230 bool thread_safe) override;
231
232 virtual void EraseUnRefEntries() override;
233
234 virtual std::string GetPrintableOptions() const override;
235
236 void TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri);
237
238 // Retrieves number of elements in LRU, for unit test purpose only
239 // not threadsafe
240 size_t TEST_GetLRUSize();
241
242 // Retrives high pri pool ratio
243 double GetHighPriPoolRatio();
244
245 private:
246 void LRU_Remove(LRUHandle* e);
247 void LRU_Insert(LRUHandle* e);
248
249 // Overflow the last entry in high-pri pool to low-pri pool until size of
250 // high-pri pool is no larger than the size specify by high_pri_pool_pct.
251 void MaintainPoolSize();
252
253 // Free some space following strict LRU policy until enough space
254 // to hold (usage_ + charge) is freed or the lru list is empty
255 // This function is not thread safe - it needs to be executed while
256 // holding the mutex_
257 void EvictFromLRU(size_t charge, autovector<LRUHandle*>* deleted);
258
259 // Initialized before use.
260 size_t capacity_;
261
262 // Memory size for entries in high-pri pool.
263 size_t high_pri_pool_usage_;
264
265 // Whether to reject insertion if cache reaches its full capacity.
266 bool strict_capacity_limit_;
267
268 // Ratio of capacity reserved for high priority cache entries.
269 double high_pri_pool_ratio_;
270
271 // High-pri pool size, equals to capacity * high_pri_pool_ratio.
272 // Remember the value to avoid recomputing each time.
273 double high_pri_pool_capacity_;
274
275 // Dummy head of LRU list.
276 // lru.prev is newest entry, lru.next is oldest entry.
277 // LRU contains items which can be evicted, ie reference only by cache
278 LRUHandle lru_;
279
280 // Pointer to head of low-pri pool in LRU list.
281 LRUHandle* lru_low_pri_;
282
283 // ------------^^^^^^^^^^^^^-----------
284 // Not frequently modified data members
285 // ------------------------------------
286 //
287 // We separate data members that are updated frequently from the ones that
288 // are not frequently updated so that they don't share the same cache line
289 // which will lead into false cache sharing
290 //
291 // ------------------------------------
292 // Frequently modified data members
293 // ------------vvvvvvvvvvvvv-----------
294 LRUHandleTable table_;
295
296 // Memory size for entries residing in the cache
297 size_t usage_;
298
299 // Memory size for entries residing only in the LRU list
300 size_t lru_usage_;
301
302 // mutex_ protects the following state.
303 // We don't count mutex_ as the cache's internal state so semantically we
304 // don't mind mutex_ invoking the non-const actions.
305 mutable port::Mutex mutex_;
306 };
307
308 class LRUCache
309 #ifdef NDEBUG
310 final
311 #endif
312 : public ShardedCache {
313 public:
314 LRUCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
315 double high_pri_pool_ratio,
316 std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
317 bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
318 CacheMetadataChargePolicy metadata_charge_policy =
319 kDontChargeCacheMetadata);
320 virtual ~LRUCache();
Name()321 virtual const char* Name() const override { return "LRUCache"; }
322 virtual CacheShard* GetShard(int shard) override;
323 virtual const CacheShard* GetShard(int shard) const override;
324 virtual void* Value(Handle* handle) override;
325 virtual size_t GetCharge(Handle* handle) const override;
326 virtual uint32_t GetHash(Handle* handle) const override;
327 virtual void DisownData() override;
328
329 // Retrieves number of elements in LRU, for unit test purpose only
330 size_t TEST_GetLRUSize();
331 // Retrives high pri pool ratio
332 double GetHighPriPoolRatio();
333
334 private:
335 LRUCacheShard* shards_ = nullptr;
336 int num_shards_ = 0;
337 };
338
339 } // namespace ROCKSDB_NAMESPACE
340