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;
  }
};

 

 

 

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