leveldb源碼學習之LRU Cache

推薦結合 leveldb-handbook 閱讀源碼

Leveldb內部通過雙向鏈表實現了一個標準版的LRUCache

include/leveldb/cache.h

// Cache 是將key映射爲value的接口. 有內部同步機制可供多線程併發安全訪問
// 可以自動淘汰元素以爲新元素騰出空間. Values 在緩存中有一定的費用. 例如一個value是變長string的cache,可以使用string的長度作爲其費用.
// 內置的 cache 實現使用LRU least-recently-used 作爲淘汰策略。調用方也可以自定義更復雜的策略。 (如scan-resistance, variable cache sizing, etc.)

#ifndef STORAGE_LEVELDB_INCLUDE_CACHE_H_
#define STORAGE_LEVELDB_INCLUDE_CACHE_H_

#include <stdint.h>

#include "leveldb/export.h"
#include "leveldb/slice.h"

namespace leveldb {

class LEVELDB_EXPORT Cache;

// 創建固定大小的新cache. 這種cache實現使用 LRU 淘汰策略.
LEVELDB_EXPORT Cache* NewLRUCache(size_t capacity);

class LEVELDB_EXPORT Cache {
 public:
  Cache() = default;

  Cache(const Cache&) = delete;
  Cache& operator=(const Cache&) = delete;

  // 通過調用傳入構造器的"deleter"函數銷燬所有元素
  virtual ~Cache();

  // 對cache中元素進行處理
  struct Handle {};

  // 往cache 插入鍵值對並賦予特定的費用.
  // 返回對應鍵值對的一個句柄handle. 使用者在鍵值對不再使用時必須調用 this->Release(handle) .
  // 當插入的元素不再使用時, key 和 value 會送至"deleter".
  virtual Handle* Insert(const Slice& key, void* value, size_t charge,
                         void (*deleter)(const Slice& key, void* value)) = 0;

  // 若 cache 中沒有 "key", 返回 nullptr.
  // Else return a handle that corresponds to the mapping.  The caller
  // must call this->Release(handle) when the returned mapping is no
  // longer needed.
  virtual Handle* Lookup(const Slice& key) = 0;

  // 釋放一個通過Lookup() 返回的鍵值對的句柄.
  // REQUIRES: handle 必須還未被釋放.
  // REQUIRES: handle 必須是 *this 返回的.
  virtual void Release(Handle* handle) = 0;

  // 返回一個成功調用 Lookup() 返回的句柄對應的value.
  // REQUIRES: handle 必須還未被釋放.
  // REQUIRES: handle 必須是 *this 返回的.
  virtual void* Value(Handle* handle) = 0;

  // 若 cache 包含key則刪除.  注意這裏元素會一直保留直到所有指向它的句柄都被釋放才刪除.
  virtual void Erase(const Slice& key) = 0;

  // 返回新的 id.  多個共享cache 的客戶端可能會使用id來劃分key空間。
  // 一般客戶端會在啓動時分配新id並將其加到key前面 .
  virtual uint64_t NewId() = 0;

  // 刪除cache中所有非使用中的元素。 有內存限制的應用可能會調用此方法來堅守內存佔用.
  // Prune()的默認實現爲空. 這裏推薦子類覆蓋默認實現. 以後的leveldb版本可能將Prune()改爲純虛函數.
  virtual void Prune() {}

  // 返回對所有元素的總費用的估計.
  virtual size_t TotalCharge() const = 0;

 private:
  void LRU_Remove(Handle* e);
  void LRU_Append(Handle* e);
  void Unref(Handle* e);

  struct Rep;
  Rep* rep_;
};

}  // namespace leveldb

#endif  // STORAGE_LEVELDB_INCLUDE_CACHE_H_

util / cache.cc

lrucache 通過元素句柄 LRUHandle  來表示元素

// 一個元素是分配在堆空間的變長結構. 按照訪問時間保存在循環雙向鏈表.
// LRUHandle是元素對應的句柄
struct LRUHandle {
  void* value; // 元素值
  void (*deleter)(const Slice&, void* value); // 元素銷燬的方法
  LRUHandle* next_hash;
  LRUHandle* next;
  LRUHandle* prev;
  size_t charge;  // TODO(opt): Only allow uint32_t?
  size_t key_length;
  bool in_cache;     // 元素是否在 cache.
  uint32_t refs;     // 引用計數,包括 cache引用如果有的話.
  uint32_t hash;     // Hash of key(); 用戶快速定位和比較
  char key_data[1];  // Beginning of key

  Slice key() const {
    // 只有當handle 代表空鏈表頭時 next_ 等於this,head不保存實際的key.
    assert(next != this);

    return Slice(key_data, key_length);
  }
};

通過簡單實現hashtable來快速定位cache中的key,經測試要比g++內部實現的hashtable更快

class HandleTable {
 public:
  HandleTable() : length_(0), elems_(0), list_(nullptr) { Resize(); }
  ~HandleTable() { delete[] list_; }

  LRUHandle* Lookup(const Slice& key, uint32_t hash) {
    return *FindPointer(key, hash);
  }

  LRUHandle* Insert(LRUHandle* h) {
    LRUHandle** ptr = FindPointer(h->key(), h->hash);
    LRUHandle* old = *ptr;
	// 若元素不存在則將元素放入鏈表末尾,否則替換原有的value
    h->next_hash = (old == nullptr ? nullptr : old->next_hash);
    *ptr = h;
    if (old == nullptr) {
      ++elems_;
      if (elems_ > length_) {
        // 總元素個數大於桶個數,因爲每個元素較大,這裏重新分配空間使得平均每個桶元素不超過1.
        Resize();
      }
    }
    return old;
  }

  LRUHandle* Remove(const Slice& key, uint32_t hash) {
    LRUHandle** ptr = FindPointer(key, hash);
    LRUHandle* result = *ptr;
    if (result != nullptr) {
      *ptr = result->next_hash;  // 元素前驅的後繼指針指向next
      --elems_;
    }
    return result;
  }

 private:
  // table 由beckets 桶數組組成,每個桶是一個cache元素的鏈表,其中的元素根據hash值放入該桶.
  uint32_t length_;
  uint32_t elems_;
  LRUHandle** list_;

  // 返回key/hash值對應的元素的指針. 若沒有這樣的元素,返回該鏈表的末尾指針(鏈表是無序的).
  LRUHandle** 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 Resize() {
    uint32_t new_length = 4;
	// 保證新的桶數量是2的冪
    while (new_length < elems_) {
      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++) { // 遍歷原來每個桶的每個元素,重新計算hash值放到新table
      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;
  }
};

 

LRU cache 實現

​
// LRU 緩存實現
// Cache 元素有 "in_cache" 成員 ,表示 cache 是否有該元素的引用。在元素沒有deleter處理時,in_cache變爲false的唯一方式是通過Erase, 或者用Insert()插入一個cache已有的key,或cache的析構函數.
// cache 內部保存兩個鏈表,所有元素只會出現在一條鏈表中。被cache 刪除但仍被客戶端引用的元素不在任何鏈表中.  這兩條鏈表是:
// - in-use: 保存當前正在被客戶端引用的元素,無序.
// - LRU:  保存不常被客戶端使用的元素, 按LRU排序
// 當檢測到cache中的元素獲得首個外部引用、或最後一個引用回收後,該元素在鏈表之間通過Ref() 和 Unref()移動,

class LRUCache {
 public:
  LRUCache();
  ~LRUCache();

  // Separate from constructor so caller can easily make an array of LRUCache
  void SetCapacity(size_t capacity) { capacity_ = capacity; }

  // Like Cache methods, but with an extra "hash" parameter.
  Cache::Handle* Insert(const Slice& key, uint32_t hash, void* value,
                        size_t charge,
                        void (*deleter)(const Slice& key, void* value));
  Cache::Handle* Lookup(const Slice& key, uint32_t hash);
  void Release(Cache::Handle* handle);
  void Erase(const Slice& key, uint32_t hash);
  void Prune();
  size_t TotalCharge() const {
    MutexLock l(&mutex_);
    return usage_;
  }

 private:
  void LRU_Remove(LRUHandle* e);
  void LRU_Append(LRUHandle* list, LRUHandle* e);
  void Ref(LRUHandle* e);
  void Unref(LRUHandle* e);
  bool FinishErase(LRUHandle* e) EXCLUSIVE_LOCKS_REQUIRED(mutex_);

  // Initialized before use.
  size_t capacity_;

  // mutex_ protects the following state.
  mutable port::Mutex mutex_;
  size_t usage_ GUARDED_BY(mutex_);

  // LRU鏈表的虛擬頭部,存放最近沒有訪問過的元素.
  // lru.prev 是最新的元素, lru.next 是最久的元素.
  // refs==1的元素且 in_cache==true,只有cache在引用元素.
  LRUHandle lru_ GUARDED_BY(mutex_);

  // in-use 鏈表的虛擬頭部,存放客戶端使用的元素.
  // refs >= 2 且 in_cache==true.
  LRUHandle in_use_ GUARDED_BY(mutex_);

  HandleTable table_ GUARDED_BY(mutex_);
};

​

構造與析構

LRUCache::LRUCache() : capacity_(0), usage_(0) {
  // Make empty circular linked lists.
  lru_.next = &lru_;
  lru_.prev = &lru_;
  in_use_.next = &in_use_;
  in_use_.prev = &in_use_;
}

LRUCache::~LRUCache() {
  assert(in_use_.next == &in_use_);  // 若還有客戶端持有元素引用則報錯
  for (LRUHandle* e = lru_.next; e != &lru_;) {
    LRUHandle* next = e->next;
    assert(e->in_cache);
    e->in_cache = false;
    assert(e->refs == 1);  // Invariant of lru_ list.
    Unref(e);
    e = next;
  }
}

對鏈表元素的訪問,若是lru鏈表元素需要移動到inuse鏈表,不需要等待淘汰

void LRUCache::Ref(LRUHandle* e) {
  if (e->refs == 1 && e->in_cache) {  // If on lru_ list, move to in_use_ list.
    LRU_Remove(e);
    LRU_Append(&in_use_, e);
  }
  e->refs++;
}

對鏈表元素解引用,引用計數爲0則需要回收,爲1則重新加入到lru鏈表等待被淘汰(最近的)

void LRUCache::Unref(LRUHandle* e) {
  assert(e->refs > 0);
  e->refs--;
  if (e->refs == 0) {  // Deallocate.
    assert(!e->in_cache);
    (*e->deleter)(e->key(), e->value);
    free(e);
  } else if (e->in_cache && e->refs == 1) {
    // No longer in use; move to lru_ list.
    LRU_Remove(e);
    LRU_Append(&lru_, e);
  }
}

鏈表元素添加與刪除

void LRUCache::LRU_Remove(LRUHandle* e) {
  e->next->prev = e->prev;
  e->prev->next = e->next;
}

void LRUCache::LRU_Append(LRUHandle* list, LRUHandle* e) {
  // Make "e" newest entry by inserting just before *list
  e->next = list;
  e->prev = list->prev;
  e->prev->next = e;
  e->next->prev = e;
}

鏈表元素查找,藉助hashtable快速定位

Cache::Handle* LRUCache::Lookup(const Slice& key, uint32_t hash) {
  MutexLock l(&mutex_);
  LRUHandle* e = table_.Lookup(key, hash);
  if (e != nullptr) {
    Ref(e);
  }
  return reinterpret_cast<Cache::Handle*>(e);
}

釋放元素、插入元素

void LRUCache::Release(Cache::Handle* handle) {
  MutexLock l(&mutex_);
  Unref(reinterpret_cast<LRUHandle*>(handle));
}

Cache::Handle* LRUCache::Insert(const Slice& key, uint32_t hash, void* value,
                                size_t charge,
                                void (*deleter)(const Slice& key,
                                                void* value)) {
  MutexLock l(&mutex_);

  LRUHandle* e =
      reinterpret_cast<LRUHandle*>(malloc(sizeof(LRUHandle) - 1 + key.size()));
  e->value = value;
  e->deleter = deleter;
  e->charge = charge;
  e->key_length = key.size();
  e->hash = hash;
  e->in_cache = false;
  e->refs = 1;  // 提供給返回的句柄 handle.
  memcpy(e->key_data, key.data(), key.size());

  if (capacity_ > 0) {
    e->refs++;  // 提供給 cache 引用.
    e->in_cache = true;
    LRU_Append(&in_use_, e); // 加入到鏈表
    usage_ += charge;
    FinishErase(table_.Insert(e));
  } else {  // don't cache. (capacity_==0 is supported and turns off caching.)
    // next is read by key() in an assert, so it must be initialized
    e->next = nullptr;
  }
  while (usage_ > capacity_ && lru_.next != &lru_) { // 元素總的費用超出了容量,且lru鏈表非空,嘗試刪除太久未訪問過的元素
    LRUHandle* old = lru_.next;
    assert(old->refs == 1);
    bool erased = FinishErase(table_.Remove(old->key(), old->hash));
    if (!erased) {  // to avoid unused variable when compiled NDEBUG
      assert(erased);
    }
  }

  return reinterpret_cast<Cache::Handle*>(e);
}

// 在table中刪除元素後再刪鏈表的元素
// If e != nullptr, finish removing *e from the cache; it has already been
// removed from the hash table.  Return whether e != nullptr.
bool LRUCache::FinishErase(LRUHandle* e) {
  if (e != nullptr) {
    assert(e->in_cache);
    LRU_Remove(e);
    e->in_cache = false;
    usage_ -= e->charge;
    Unref(e);
  }
  return e != nullptr;
}

清空lru鏈表

void LRUCache::Prune() {
  MutexLock l(&mutex_);
  while (lru_.next != &lru_) {
    LRUHandle* e = lru_.next;
    assert(e->refs == 1);
    bool erased = FinishErase(table_.Remove(e->key(), e->hash));
    if (!erased) {  // to avoid unused variable when compiled NDEBUG
      assert(erased);
    }
  }
}

 

分片LRU cache,在以上cache基礎上,新增根據key的hash值分配到不同LRUcache的ShardedLRUCache

static const int kNumShardBits = 4;
static const int kNumShards = 1 << kNumShardBits;

class ShardedLRUCache : public Cache {
 private:
  LRUCache shard_[kNumShards];
  port::Mutex id_mutex_;
  uint64_t last_id_;

  static inline uint32_t HashSlice(const Slice& s) {
    return Hash(s.data(), s.size(), 0);
  }

  static uint32_t Shard(uint32_t hash) { return hash >> (32 - kNumShardBits); }

 public:
  explicit ShardedLRUCache(size_t capacity) : last_id_(0) {
    const size_t per_shard = (capacity + (kNumShards - 1)) / kNumShards;
    for (int s = 0; s < kNumShards; s++) {
      shard_[s].SetCapacity(per_shard);
    }
  }
  ~ShardedLRUCache() override {}
  Handle* Insert(const Slice& key, void* value, size_t charge,
                 void (*deleter)(const Slice& key, void* value)) override {
    const uint32_t hash = HashSlice(key);
    return shard_[Shard(hash)].Insert(key, hash, value, charge, deleter);
  }
  Handle* Lookup(const Slice& key) override {
    const uint32_t hash = HashSlice(key);
    return shard_[Shard(hash)].Lookup(key, hash);
  }
  void Release(Handle* handle) override {
    LRUHandle* h = reinterpret_cast<LRUHandle*>(handle);
    shard_[Shard(h->hash)].Release(handle);
  }
  void Erase(const Slice& key) override {
    const uint32_t hash = HashSlice(key);
    shard_[Shard(hash)].Erase(key, hash);
  }
  void* Value(Handle* handle) override {
    return reinterpret_cast<LRUHandle*>(handle)->value;
  }
  uint64_t NewId() override {
    MutexLock l(&id_mutex_);
    return ++(last_id_);
  }
  void Prune() override {
    for (int s = 0; s < kNumShards; s++) {
      shard_[s].Prune();
    }
  }
  size_t TotalCharge() const override {
    size_t total = 0;
    for (int s = 0; s < kNumShards; s++) {
      total += shard_[s].TotalCharge();
    }
    return total;
  }
};

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章