hash_table_evictable.h 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Copyright (c) 2013, 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. #pragma once
  7. #include <functional>
  8. #include "util/random.h"
  9. #include "utilities/persistent_cache/hash_table.h"
  10. #include "utilities/persistent_cache/lrulist.h"
  11. namespace ROCKSDB_NAMESPACE {
  12. // Evictable Hash Table
  13. //
  14. // Hash table index where least accessed (or one of the least accessed) elements
  15. // can be evicted.
  16. //
  17. // Please note EvictableHashTable can only be created for pointer type objects
  18. template <class T, class Hash, class Equal>
  19. class EvictableHashTable : private HashTable<T*, Hash, Equal> {
  20. public:
  21. using hash_table = HashTable<T*, Hash, Equal>;
  22. explicit EvictableHashTable(const size_t capacity = 1024 * 1024,
  23. const float load_factor = 2.0,
  24. const uint32_t nlocks = 256)
  25. : HashTable<T*, Hash, Equal>(capacity, load_factor, nlocks),
  26. lru_lists_(new LRUList<T>[hash_table::nlocks_]) {
  27. assert(lru_lists_);
  28. }
  29. virtual ~EvictableHashTable() { AssertEmptyLRU(); }
  30. //
  31. // Insert given record to hash table (and LRU list)
  32. //
  33. bool Insert(T* t) {
  34. const uint64_t h = Hash()(t);
  35. typename hash_table::Bucket& bucket = GetBucket(h);
  36. LRUListType& lru = GetLRUList(h);
  37. port::RWMutex& lock = GetMutex(h);
  38. WriteLock _(&lock);
  39. if (hash_table::Insert(&bucket, t)) {
  40. lru.Push(t);
  41. return true;
  42. }
  43. return false;
  44. }
  45. //
  46. // Lookup hash table
  47. //
  48. // Please note that read lock should be held by the caller. This is because
  49. // the caller owns the data, and should hold the read lock as long as he
  50. // operates on the data.
  51. bool Find(T* t, T** ret) {
  52. const uint64_t h = Hash()(t);
  53. typename hash_table::Bucket& bucket = GetBucket(h);
  54. LRUListType& lru = GetLRUList(h);
  55. port::RWMutex& lock = GetMutex(h);
  56. ReadLock _(&lock);
  57. if (hash_table::Find(&bucket, t, ret)) {
  58. ++(*ret)->refs_;
  59. lru.Touch(*ret);
  60. return true;
  61. }
  62. return false;
  63. }
  64. //
  65. // Evict one of the least recently used object
  66. //
  67. T* Evict(const std::function<void(T*)>& fn = nullptr) {
  68. uint32_t random = Random::GetTLSInstance()->Next();
  69. const size_t start_idx = random % hash_table::nlocks_;
  70. T* t = nullptr;
  71. // iterate from start_idx .. 0 .. start_idx
  72. for (size_t i = 0; !t && i < hash_table::nlocks_; ++i) {
  73. const size_t idx = (start_idx + i) % hash_table::nlocks_;
  74. WriteLock _(&hash_table::locks_[idx]);
  75. LRUListType& lru = lru_lists_[idx];
  76. if (!lru.IsEmpty() && (t = lru.Pop()) != nullptr) {
  77. assert(!t->refs_);
  78. // We got an item to evict, erase from the bucket
  79. const uint64_t h = Hash()(t);
  80. typename hash_table::Bucket& bucket = GetBucket(h);
  81. T* tmp = nullptr;
  82. bool status = hash_table::Erase(&bucket, t, &tmp);
  83. assert(t == tmp);
  84. (void)status;
  85. assert(status);
  86. if (fn) {
  87. fn(t);
  88. }
  89. break;
  90. }
  91. assert(!t);
  92. }
  93. return t;
  94. }
  95. void Clear(void (*fn)(T*)) {
  96. for (uint32_t i = 0; i < hash_table::nbuckets_; ++i) {
  97. const uint32_t lock_idx = i % hash_table::nlocks_;
  98. WriteLock _(&hash_table::locks_[lock_idx]);
  99. auto& lru_list = lru_lists_[lock_idx];
  100. auto& bucket = hash_table::buckets_[i];
  101. for (auto* t : bucket.list_) {
  102. lru_list.Unlink(t);
  103. (*fn)(t);
  104. }
  105. bucket.list_.clear();
  106. }
  107. // make sure that all LRU lists are emptied
  108. AssertEmptyLRU();
  109. }
  110. void AssertEmptyLRU() {
  111. #ifndef NDEBUG
  112. for (uint32_t i = 0; i < hash_table::nlocks_; ++i) {
  113. WriteLock _(&hash_table::locks_[i]);
  114. auto& lru_list = lru_lists_[i];
  115. assert(lru_list.IsEmpty());
  116. }
  117. #endif
  118. }
  119. //
  120. // Fetch the mutex associated with a key
  121. // This call is used to hold the lock for a given data for extended period of
  122. // time.
  123. port::RWMutex* GetMutex(T* t) { return hash_table::GetMutex(t); }
  124. private:
  125. using LRUListType = LRUList<T>;
  126. typename hash_table::Bucket& GetBucket(const uint64_t h) {
  127. const uint32_t bucket_idx = h % hash_table::nbuckets_;
  128. return hash_table::buckets_[bucket_idx];
  129. }
  130. LRUListType& GetLRUList(const uint64_t h) {
  131. const uint32_t bucket_idx = h % hash_table::nbuckets_;
  132. const uint32_t lock_idx = bucket_idx % hash_table::nlocks_;
  133. return lru_lists_[lock_idx];
  134. }
  135. port::RWMutex& GetMutex(const uint64_t h) {
  136. const uint32_t bucket_idx = h % hash_table::nbuckets_;
  137. const uint32_t lock_idx = bucket_idx % hash_table::nlocks_;
  138. return hash_table::locks_[lock_idx];
  139. }
  140. std::unique_ptr<LRUListType[]> lru_lists_;
  141. };
  142. } // namespace ROCKSDB_NAMESPACE