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