leveldb cache 分析
先从cache.h开始看,提供了创建cache实例的方法NewLRUCache(size_t capacity)。创建出的cache本身支持插入、查找、释放等操作,进入NewLRUCache的实现可以发现内部是一个LRUCache,接下来进行详细分析
1.类结构图
(一个小知识,在读代码过程会发现很多GUARDED_BY,其定义是根据是否有__clang__来进行空定义或其他定义。这个那是Clang Thread Safety Analysis中定义的,是数据成员的属性,该属性声明数据成员受给定功能保护。读共享,写互斥。可以进行静态分析)
2.LRUCache
从底层往上看,先是LRUCache,其内部包含有容量capaicty,锁mutex,已使用的容量usage_,LRUHandle的_lru(prev指向第一个节点,next,指向最后一个节点)和in_use_(存储正在使用的节点),在然后有个HandleTable(自己实现的hashtable来加速查找)。LRUCache其实就是lru的实现。先看内部的数据结。
2.1 数据结构
2.1.1 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; // Whether entry is in the cache.
uint32_t refs; // References, including cache reference, if present.
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
char key_data[1]; // Beginning of key
Slice key() const {
// next_ is only equal to this if the LRU handle is the list head of an
// empty list. List heads never have meaningful keys.
assert(next != this);
return Slice(key_data, key_length);
}
};
内部数据如上,使用key的开始地址和长度来记录key,因为使用自定义的Slice,所以不能直接使用指针不记录长度,方便后续处理。
下面简单看一下插入时申请空间操作
//减去1即为char[1],加上key的size
LRUHandle* e = reinterpret_cast<LRUHandle*>(
malloc(sizeof(LRUHandle)-1 + key.size()));
释放时URef函数
if (e->refs == 0) { // Deallocate.
assert(!e->in_cache);
(*e->deleter)(e->key(), e->value);
free(e);
2.1.2 HandleTable
还是从数据来看
// 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,第一级相同的放一起(相当于开链表方式解决冲突)
2.2 流程图
下面简单看一下流程。
2.2.1 插入流程
2.2.2 移除流程
2.2.3 查找流程
相对简单直接在hashtable中查找
3.ShardedLRUCache
其实主要都是使用了LRUCache的函数,主要改进在于使用LRUCache[],使用多个来达到分段的效果,减少竞争(因为涉及互斥)