levelDB源码笔记(3)-cache
来源:互联网 发布:如何看淘宝总消费金额 编辑:程序博客网 时间:2024/06/05 13:20
levelDB实现的cache是LRU(Least Recently Used 近期最少使用)算法。其实现在ShardedLRUCache中,类成员主要有
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); } } virtual ~ShardedLRUCache() { } virtual Handle* Insert(const Slice& key, void* value, size_t charge, void (*deleter)(const Slice& key, void* value)) { const uint32_t hash = HashSlice(key); return shard_[Shard(hash)].Insert(key, hash, value, charge, deleter); } virtual Handle* Lookup(const Slice& key) { const uint32_t hash = HashSlice(key); return shard_[Shard(hash)].Lookup(key, hash); } virtual void Release(Handle* handle); virtual void Erase(const Slice& key); virtual void* Value(Handle* handle);};其主要的成员是LRUCache shard_[kNumShards];
每个SharedLRUCache包含多个LRUCache,查找Key时首先计算key属于哪一个分片hash=Shard(HashSlice(key)) ,然后在相应的shard_[hash]上进行查找。分片采用hash值的高位,这是一种常见的方法。使用多个LRUCache上,可以减少多线程的锁开销。对了,cache里都使用了mutex,ref等技术,保证了线程安全
LRUCache用的是个比较标准的算法。
class LRUCache { public: 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); private: // Initialized before use. size_t capacity_; // mutex_ protects the following state. port::Mutex mutex_; size_t usage_; // Dummy head of LRU list. // lru.prev is newest entry, lru.next is oldest entry. LRUHandle lru_; HandleTable table_;}LRUCache需要通过key来获取对应的value(或null表示missing),每个数据对的指针保存在一个Handle节点里。
其主要成员包括一个按使用时间排列的双向链表lru。双向列表适合插入删除,特别是可以快速删除最老的块。其中lru.prev指向最新使用的块,lru.next指向最老的块。capacity_是cache最大长度,useage_则是当前已用,当useage_>capicity_,则删掉最老的一些块,释放内存。
另一个成员table_。这是一个自己实现的哈希表(内部实现采用二维链表),可以通过key找到对应的块。
双向链表和handletable的单个节点都是LRUHandle。所以LRUHandle包含了数据key/value,双向列表需要的前向,后向指针,以及handletable需要的指针next_hash
其成员函数定义如下:
struct LRUHandle { void* value; void (*deleter)(const Slice&, void* value); LRUHandle* next_hash; //handletable需要的指针 LRUHandle* next; //双向链表需要的next指针 LRUHandle* prev; //双向链表需要的prev指针 size_t charge; // 本块的数据大小(似乎是只有value的大小?不过这个不重要) size_t key_length; uint32_t refs; uint32_t hash; // Hash of key(); used for fast sharding and comparisons char key_data[1]; // Beginning of key......};这里用了几个技巧
1. key_data[1] .这个必须放在结构的最后一个成员。当申请一个LRUHandle变量时,申请的长度是sizeof(LRUHandle)-1 + key.size()
LRUHandle* e = reinterpret_cast<LRUHandle*>( malloc(sizeof(LRUHandle)-1 + key.size()));用key_data可以索引到后面一些多出来的内存,等于实现了一个变长的buffer。
2. refs。
我们知道,cache随时可能被替换。当一个Handle块被从cache删除时,可能外部另一个线程正在使用它,这时候我们直接释放掉对应的内存,就会出错。
这里用ref配合delete函数来解决这个问题。当insert一个块时,ref=2。因为此时外部正在访问这个块,同时cache内部保留了这个块。另一个外部使用函数是lookup。每次外部从cache中获取一个handle,其ref++。当外部使用完毕后,必须调用unref函数,ref--。从cache中删除一个块,同样造成ref--。只有当ref<=0,才真正调用delete释放内存,同时修改对应cache的大小(useage)
3.将双向列表和hashtable使用同一个handle结构作为基本节点。
Handletable的主要成员变量是
private: // The table consists of an array of buckets where each bucket is // a linked list of cache entries that hash into the bucket. uint32_t length_; uint32_t elems_; LRUHandle** list_;查找方式如下:
LRUHandle** FindPointer(const Slice& key, uint32_t hash) { LRUHandle** ptr = &list_[hash & (length_ - 1)]; while (*ptr != NULL && ((*ptr)->hash != hash || key != (*ptr)->key())) { ptr = &(*ptr)->next_hash; } return ptr; }其实相当于采用链表法避免冲突的哈希表。
- levelDB源码笔记(3)-cache
- Leveldb源码分析3 Cache
- levelDB源码分析-Cache(LRUCache、HashTable)
- leveldb源码分析:Cache
- leveldb源码学习 2 - Cache
- levelDB源码笔记(1)-TEST
- levelDB源码笔记(2)-Arena
- levelDB源码笔记(4)- SkipList
- LevelDB:Cache源码精读——缓存
- leveldb源码学习——Cache
- leveldb源码阅读分析笔记
- Leveldb源码分析--3
- Leveldb源码分析--3
- LevelDB源码剖析之Cache(HashTable LRUCache ShardedLRUCache)
- leveldb源码学习 3 - Comparator
- leveldb之Cache
- leveldb之cache
- leveldb学习:Cache
- SP2010开发和VS2010专家"食谱"--第二章节--工作流(2)--部署工作流的InfoPath表单
- 第一章 大败家系统
- 第二章 提亲
- Android开发小生(四)
- 关于Win2008系统DNS服务器安装配置操作教程
- levelDB源码笔记(3)-cache
- 从一个简单的计时器,看Java接口的作用
- Meet 4 of Europe’s fastest-growing tech firms: Showpad, Akamon, KelBillet and Eyefreight
- 我为什么喜欢Go语言
- 设置Ubuntu上的MySQL可以远程访问
- Light oj 1007 - Mathematically Hard
- 15分钟学会使用Git和远程代码库
- 第三章 败家可是个技术活
- SP2010开发和VS2010专家"食谱"--第二章节--工作流(3)--创建工作流里的任务