| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
- // This source code is licensed under both the GPLv2 (found in the
- // COPYING file in the root directory) and Apache 2.0 License
- // (found in the LICENSE.Apache file in the root directory).
- //
- // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file. See the AUTHORS file for names of contributors.
- #include "cache/lru_cache.h"
- #include <assert.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string>
- #include "util/mutexlock.h"
- namespace ROCKSDB_NAMESPACE {
- LRUHandleTable::LRUHandleTable() : list_(nullptr), length_(0), elems_(0) {
- Resize();
- }
- LRUHandleTable::~LRUHandleTable() {
- ApplyToAllCacheEntries([](LRUHandle* h) {
- if (!h->HasRefs()) {
- h->Free();
- }
- });
- delete[] list_;
- }
- LRUHandle* LRUHandleTable::Lookup(const Slice& key, uint32_t hash) {
- return *FindPointer(key, hash);
- }
- LRUHandle* LRUHandleTable::Insert(LRUHandle* h) {
- LRUHandle** ptr = FindPointer(h->key(), h->hash);
- LRUHandle* old = *ptr;
- h->next_hash = (old == nullptr ? nullptr : old->next_hash);
- *ptr = h;
- if (old == nullptr) {
- ++elems_;
- if (elems_ > length_) {
- // Since each cache entry is fairly large, we aim for a small
- // average linked list length (<= 1).
- Resize();
- }
- }
- return old;
- }
- LRUHandle* LRUHandleTable::Remove(const Slice& key, uint32_t hash) {
- LRUHandle** ptr = FindPointer(key, hash);
- LRUHandle* result = *ptr;
- if (result != nullptr) {
- *ptr = result->next_hash;
- --elems_;
- }
- return result;
- }
- LRUHandle** LRUHandleTable::FindPointer(const Slice& key, uint32_t hash) {
- LRUHandle** ptr = &list_[hash & (length_ - 1)];
- while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
- ptr = &(*ptr)->next_hash;
- }
- return ptr;
- }
- void LRUHandleTable::Resize() {
- uint32_t new_length = 16;
- while (new_length < elems_ * 1.5) {
- new_length *= 2;
- }
- LRUHandle** new_list = new LRUHandle*[new_length];
- memset(new_list, 0, sizeof(new_list[0]) * new_length);
- uint32_t count = 0;
- for (uint32_t i = 0; i < length_; i++) {
- LRUHandle* h = list_[i];
- while (h != nullptr) {
- LRUHandle* next = h->next_hash;
- uint32_t hash = h->hash;
- LRUHandle** ptr = &new_list[hash & (new_length - 1)];
- h->next_hash = *ptr;
- *ptr = h;
- h = next;
- count++;
- }
- }
- assert(elems_ == count);
- delete[] list_;
- list_ = new_list;
- length_ = new_length;
- }
- LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
- double high_pri_pool_ratio,
- bool use_adaptive_mutex,
- CacheMetadataChargePolicy metadata_charge_policy)
- : capacity_(0),
- high_pri_pool_usage_(0),
- strict_capacity_limit_(strict_capacity_limit),
- high_pri_pool_ratio_(high_pri_pool_ratio),
- high_pri_pool_capacity_(0),
- usage_(0),
- lru_usage_(0),
- mutex_(use_adaptive_mutex) {
- set_metadata_charge_policy(metadata_charge_policy);
- // Make empty circular linked list
- lru_.next = &lru_;
- lru_.prev = &lru_;
- lru_low_pri_ = &lru_;
- SetCapacity(capacity);
- }
- void LRUCacheShard::EraseUnRefEntries() {
- autovector<LRUHandle*> last_reference_list;
- {
- MutexLock l(&mutex_);
- while (lru_.next != &lru_) {
- LRUHandle* old = lru_.next;
- // LRU list contains only elements which can be evicted
- assert(old->InCache() && !old->HasRefs());
- LRU_Remove(old);
- table_.Remove(old->key(), old->hash);
- old->SetInCache(false);
- size_t total_charge = old->CalcTotalCharge(metadata_charge_policy_);
- assert(usage_ >= total_charge);
- usage_ -= total_charge;
- last_reference_list.push_back(old);
- }
- }
- for (auto entry : last_reference_list) {
- entry->Free();
- }
- }
- void LRUCacheShard::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
- bool thread_safe) {
- const auto applyCallback = [&]() {
- table_.ApplyToAllCacheEntries(
- [callback](LRUHandle* h) { callback(h->value, h->charge); });
- };
- if (thread_safe) {
- MutexLock l(&mutex_);
- applyCallback();
- } else {
- applyCallback();
- }
- }
- void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri) {
- MutexLock l(&mutex_);
- *lru = &lru_;
- *lru_low_pri = lru_low_pri_;
- }
- size_t LRUCacheShard::TEST_GetLRUSize() {
- MutexLock l(&mutex_);
- LRUHandle* lru_handle = lru_.next;
- size_t lru_size = 0;
- while (lru_handle != &lru_) {
- lru_size++;
- lru_handle = lru_handle->next;
- }
- return lru_size;
- }
- double LRUCacheShard::GetHighPriPoolRatio() {
- MutexLock l(&mutex_);
- return high_pri_pool_ratio_;
- }
- void LRUCacheShard::LRU_Remove(LRUHandle* e) {
- assert(e->next != nullptr);
- assert(e->prev != nullptr);
- if (lru_low_pri_ == e) {
- lru_low_pri_ = e->prev;
- }
- e->next->prev = e->prev;
- e->prev->next = e->next;
- e->prev = e->next = nullptr;
- size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
- assert(lru_usage_ >= total_charge);
- lru_usage_ -= total_charge;
- if (e->InHighPriPool()) {
- assert(high_pri_pool_usage_ >= total_charge);
- high_pri_pool_usage_ -= total_charge;
- }
- }
- void LRUCacheShard::LRU_Insert(LRUHandle* e) {
- assert(e->next == nullptr);
- assert(e->prev == nullptr);
- size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
- if (high_pri_pool_ratio_ > 0 && (e->IsHighPri() || e->HasHit())) {
- // Inset "e" to head of LRU list.
- e->next = &lru_;
- e->prev = lru_.prev;
- e->prev->next = e;
- e->next->prev = e;
- e->SetInHighPriPool(true);
- high_pri_pool_usage_ += total_charge;
- MaintainPoolSize();
- } else {
- // Insert "e" to the head of low-pri pool. Note that when
- // high_pri_pool_ratio is 0, head of low-pri pool is also head of LRU list.
- e->next = lru_low_pri_->next;
- e->prev = lru_low_pri_;
- e->prev->next = e;
- e->next->prev = e;
- e->SetInHighPriPool(false);
- lru_low_pri_ = e;
- }
- lru_usage_ += total_charge;
- }
- void LRUCacheShard::MaintainPoolSize() {
- while (high_pri_pool_usage_ > high_pri_pool_capacity_) {
- // Overflow last entry in high-pri pool to low-pri pool.
- lru_low_pri_ = lru_low_pri_->next;
- assert(lru_low_pri_ != &lru_);
- lru_low_pri_->SetInHighPriPool(false);
- size_t total_charge =
- lru_low_pri_->CalcTotalCharge(metadata_charge_policy_);
- assert(high_pri_pool_usage_ >= total_charge);
- high_pri_pool_usage_ -= total_charge;
- }
- }
- void LRUCacheShard::EvictFromLRU(size_t charge,
- autovector<LRUHandle*>* deleted) {
- while ((usage_ + charge) > capacity_ && lru_.next != &lru_) {
- LRUHandle* old = lru_.next;
- // LRU list contains only elements which can be evicted
- assert(old->InCache() && !old->HasRefs());
- LRU_Remove(old);
- table_.Remove(old->key(), old->hash);
- old->SetInCache(false);
- size_t old_total_charge = old->CalcTotalCharge(metadata_charge_policy_);
- assert(usage_ >= old_total_charge);
- usage_ -= old_total_charge;
- deleted->push_back(old);
- }
- }
- void LRUCacheShard::SetCapacity(size_t capacity) {
- autovector<LRUHandle*> last_reference_list;
- {
- MutexLock l(&mutex_);
- capacity_ = capacity;
- high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
- EvictFromLRU(0, &last_reference_list);
- }
- // Free the entries outside of mutex for performance reasons
- for (auto entry : last_reference_list) {
- entry->Free();
- }
- }
- void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
- MutexLock l(&mutex_);
- strict_capacity_limit_ = strict_capacity_limit;
- }
- Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
- MutexLock l(&mutex_);
- LRUHandle* e = table_.Lookup(key, hash);
- if (e != nullptr) {
- assert(e->InCache());
- if (!e->HasRefs()) {
- // The entry is in LRU since it's in hash and has no external references
- LRU_Remove(e);
- }
- e->Ref();
- e->SetHit();
- }
- return reinterpret_cast<Cache::Handle*>(e);
- }
- bool LRUCacheShard::Ref(Cache::Handle* h) {
- LRUHandle* e = reinterpret_cast<LRUHandle*>(h);
- MutexLock l(&mutex_);
- // To create another reference - entry must be already externally referenced
- assert(e->HasRefs());
- e->Ref();
- return true;
- }
- void LRUCacheShard::SetHighPriorityPoolRatio(double high_pri_pool_ratio) {
- MutexLock l(&mutex_);
- high_pri_pool_ratio_ = high_pri_pool_ratio;
- high_pri_pool_capacity_ = capacity_ * high_pri_pool_ratio_;
- MaintainPoolSize();
- }
- bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
- if (handle == nullptr) {
- return false;
- }
- LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
- bool last_reference = false;
- {
- MutexLock l(&mutex_);
- last_reference = e->Unref();
- if (last_reference && e->InCache()) {
- // The item is still in cache, and nobody else holds a reference to it
- if (usage_ > capacity_ || force_erase) {
- // The LRU list must be empty since the cache is full
- assert(lru_.next == &lru_ || force_erase);
- // Take this opportunity and remove the item
- table_.Remove(e->key(), e->hash);
- e->SetInCache(false);
- } else {
- // Put the item back on the LRU list, and don't free it
- LRU_Insert(e);
- last_reference = false;
- }
- }
- if (last_reference) {
- size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
- assert(usage_ >= total_charge);
- usage_ -= total_charge;
- }
- }
- // Free the entry here outside of mutex for performance reasons
- if (last_reference) {
- e->Free();
- }
- return last_reference;
- }
- Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
- size_t charge,
- void (*deleter)(const Slice& key, void* value),
- Cache::Handle** handle, Cache::Priority priority) {
- // Allocate the memory here outside of the mutex
- // If the cache is full, we'll have to release it
- // It shouldn't happen very often though.
- LRUHandle* e = reinterpret_cast<LRUHandle*>(
- new char[sizeof(LRUHandle) - 1 + key.size()]);
- Status s = Status::OK();
- autovector<LRUHandle*> last_reference_list;
- e->value = value;
- e->deleter = deleter;
- e->charge = charge;
- e->key_length = key.size();
- e->flags = 0;
- e->hash = hash;
- e->refs = 0;
- e->next = e->prev = nullptr;
- e->SetInCache(true);
- e->SetPriority(priority);
- memcpy(e->key_data, key.data(), key.size());
- size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
- {
- MutexLock l(&mutex_);
- // Free the space following strict LRU policy until enough space
- // is freed or the lru list is empty
- EvictFromLRU(total_charge, &last_reference_list);
- if ((usage_ + total_charge) > capacity_ &&
- (strict_capacity_limit_ || handle == nullptr)) {
- if (handle == nullptr) {
- // Don't insert the entry but still return ok, as if the entry inserted
- // into cache and get evicted immediately.
- e->SetInCache(false);
- last_reference_list.push_back(e);
- } else {
- delete[] reinterpret_cast<char*>(e);
- *handle = nullptr;
- s = Status::Incomplete("Insert failed due to LRU cache being full.");
- }
- } else {
- // Insert into the cache. Note that the cache might get larger than its
- // capacity if not enough space was freed up.
- LRUHandle* old = table_.Insert(e);
- usage_ += total_charge;
- if (old != nullptr) {
- assert(old->InCache());
- old->SetInCache(false);
- if (!old->HasRefs()) {
- // old is on LRU because it's in cache and its reference count is 0
- LRU_Remove(old);
- size_t old_total_charge =
- old->CalcTotalCharge(metadata_charge_policy_);
- assert(usage_ >= old_total_charge);
- usage_ -= old_total_charge;
- last_reference_list.push_back(old);
- }
- }
- if (handle == nullptr) {
- LRU_Insert(e);
- } else {
- e->Ref();
- *handle = reinterpret_cast<Cache::Handle*>(e);
- }
- }
- }
- // Free the entries here outside of mutex for performance reasons
- for (auto entry : last_reference_list) {
- entry->Free();
- }
- return s;
- }
- void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
- LRUHandle* e;
- bool last_reference = false;
- {
- MutexLock l(&mutex_);
- e = table_.Remove(key, hash);
- if (e != nullptr) {
- assert(e->InCache());
- e->SetInCache(false);
- if (!e->HasRefs()) {
- // The entry is in LRU since it's in hash and has no external references
- LRU_Remove(e);
- size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
- assert(usage_ >= total_charge);
- usage_ -= total_charge;
- last_reference = true;
- }
- }
- }
- // Free the entry here outside of mutex for performance reasons
- // last_reference will only be true if e != nullptr
- if (last_reference) {
- e->Free();
- }
- }
- size_t LRUCacheShard::GetUsage() const {
- MutexLock l(&mutex_);
- return usage_;
- }
- size_t LRUCacheShard::GetPinnedUsage() const {
- MutexLock l(&mutex_);
- assert(usage_ >= lru_usage_);
- return usage_ - lru_usage_;
- }
- std::string LRUCacheShard::GetPrintableOptions() const {
- const int kBufferSize = 200;
- char buffer[kBufferSize];
- {
- MutexLock l(&mutex_);
- snprintf(buffer, kBufferSize, " high_pri_pool_ratio: %.3lf\n",
- high_pri_pool_ratio_);
- }
- return std::string(buffer);
- }
- LRUCache::LRUCache(size_t capacity, int num_shard_bits,
- bool strict_capacity_limit, double high_pri_pool_ratio,
- std::shared_ptr<MemoryAllocator> allocator,
- bool use_adaptive_mutex,
- CacheMetadataChargePolicy metadata_charge_policy)
- : ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
- std::move(allocator)) {
- num_shards_ = 1 << num_shard_bits;
- shards_ = reinterpret_cast<LRUCacheShard*>(
- port::cacheline_aligned_alloc(sizeof(LRUCacheShard) * num_shards_));
- size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
- for (int i = 0; i < num_shards_; i++) {
- new (&shards_[i])
- LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio,
- use_adaptive_mutex, metadata_charge_policy);
- }
- }
- LRUCache::~LRUCache() {
- if (shards_ != nullptr) {
- assert(num_shards_ > 0);
- for (int i = 0; i < num_shards_; i++) {
- shards_[i].~LRUCacheShard();
- }
- port::cacheline_aligned_free(shards_);
- }
- }
- CacheShard* LRUCache::GetShard(int shard) {
- return reinterpret_cast<CacheShard*>(&shards_[shard]);
- }
- const CacheShard* LRUCache::GetShard(int shard) const {
- return reinterpret_cast<CacheShard*>(&shards_[shard]);
- }
- void* LRUCache::Value(Handle* handle) {
- return reinterpret_cast<const LRUHandle*>(handle)->value;
- }
- size_t LRUCache::GetCharge(Handle* handle) const {
- return reinterpret_cast<const LRUHandle*>(handle)->charge;
- }
- uint32_t LRUCache::GetHash(Handle* handle) const {
- return reinterpret_cast<const LRUHandle*>(handle)->hash;
- }
- void LRUCache::DisownData() {
- // Do not drop data if compile with ASAN to suppress leak warning.
- #if defined(__clang__)
- #if !defined(__has_feature) || !__has_feature(address_sanitizer)
- shards_ = nullptr;
- num_shards_ = 0;
- #endif
- #else // __clang__
- #ifndef __SANITIZE_ADDRESS__
- shards_ = nullptr;
- num_shards_ = 0;
- #endif // !__SANITIZE_ADDRESS__
- #endif // __clang__
- }
- size_t LRUCache::TEST_GetLRUSize() {
- size_t lru_size_of_all_shards = 0;
- for (int i = 0; i < num_shards_; i++) {
- lru_size_of_all_shards += shards_[i].TEST_GetLRUSize();
- }
- return lru_size_of_all_shards;
- }
- double LRUCache::GetHighPriPoolRatio() {
- double result = 0.0;
- if (num_shards_ > 0) {
- result = shards_[0].GetHighPriPoolRatio();
- }
- return result;
- }
- std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
- return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
- cache_opts.strict_capacity_limit,
- cache_opts.high_pri_pool_ratio,
- cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
- cache_opts.metadata_charge_policy);
- }
- std::shared_ptr<Cache> NewLRUCache(
- size_t capacity, int num_shard_bits, bool strict_capacity_limit,
- double high_pri_pool_ratio,
- std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
- CacheMetadataChargePolicy metadata_charge_policy) {
- if (num_shard_bits >= 20) {
- return nullptr; // the cache cannot be sharded into too many fine pieces
- }
- if (high_pri_pool_ratio < 0.0 || high_pri_pool_ratio > 1.0) {
- // invalid high_pri_pool_ratio
- return nullptr;
- }
- if (num_shard_bits < 0) {
- num_shard_bits = GetDefaultCacheShardBits(capacity);
- }
- return std::make_shared<LRUCache>(
- capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
- std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy);
- }
- } // namespace ROCKSDB_NAMESPACE
|