LRUCache

来源:互联网 发布:电脑怎么用手机网络 编辑:程序博客网 时间:2024/06/11 06:54
// 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.//// A Cache is an interface that maps keys to values.  It has internal// synchronization and may be safely accessed concurrently from// multiple threads.  It may automatically evict entries to make room// for new entries.  Values have a specified charge against the cache// capacity.  For example, a cache where the values are variable// length strings, may use the length of the string as the charge for// the string.//// A builtin cache implementation with a least-recently-used eviction// policy is provided.  Clients may use their own implementations if// they want something more sophisticated (like scan-resistance, a// custom eviction policy, variable cache sizing, etc.)#ifndef STORAGE_LEVELDB_INCLUDE_CACHE_H_#define STORAGE_LEVELDB_INCLUDE_CACHE_H_#include <stdint.h>#include "leveldb/slice.h"namespace leveldb {class Cache;// Create a new cache with a fixed size capacity.  This implementation// of Cache uses a least-recently-used eviction policy.extern CacheNewLRUCache(size_t capacity);class Cache{public:Cache() {}// Destroys all existing entries by calling the "deleter"// function that was passed to the constructor.virtual ~Cache();// Opaque handle to an entry stored in the cache.struct Handle { };// Insert a mapping from key->value into the cache and assign it// the specified charge against the total cache capacity.//// Returns a handle that corresponds to the mapping.  The caller// must call this->Release(handle) when the returned mapping is no// longer needed.//// When the inserted entry is no longer needed, the key and// value will be passed to "deleter".virtual HandleInsert(const Slicekeyvoid* value, size_t charge,void (*deleter)(const Slicekeyvoid* value)) = 0;// If the cache has no mapping for "key", returns NULL.//// 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 HandleLookup(const Slicekey) = 0;// Release a mapping returned by a previous Lookup().// REQUIRES: handle must not have been released yet.// REQUIRES: handle must have been returned by a method on *this.virtual void Release(Handle* handle) = 0;// Return the value encapsulated in a handle returned by a// successful Lookup().// REQUIRES: handle must not have been released yet.// REQUIRES: handle must have been returned by a method on *this.virtual voidValue(Handle* handle) = 0;// If the cache contains entry for key, erase it.  Note that the// underlying entry will be kept around until all existing handles// to it have been released.virtual void Erase(const Slicekey) = 0;// Return a new numeric id.  May be used by multiple clients who are// sharing the same cache to partition the key space.  Typically the// client will allocate a new id at startup and prepend the id to// its cache keys.virtual uint64_t NewId() = 0;// Remove all cache entries that are not actively in use.  Memory-constrained// applications may wish to call this method to reduce memory usage.// Default implementation of Prune() does nothing.  Subclasses are strongly// encouraged to override the default implementation.  A future release of// leveldb may change Prune() to a pure abstract method.virtual void Prune() {}// Return an estimate of the combined charges of all elements stored in the// cache.virtual size_t TotalCharge() const = 0;private:void LRU_RemoveHandle* e );void LRU_AppendHandle* e );void UnrefHandle* e );struct Rep;Rep* rep_;// No copying allowedCacheconst Cache& );void operator=( const Cache& );};}  // namespace leveldb#endif  // STORAGE_LEVELDB_INCLUDE_CACHE_H_
// 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 <assert.h>#include <stdio.h>#include <stdlib.h>#include "leveldb/cache.h"#include "port/port.h"#include "util/hash.h"#include "util/mutexlock.h"/*首先要看这个类是为解决什么问题而建立的。参考:http://blog.csdn.net/u012658346/article/details/45486051key value cache,要高效率的get和set的话,必然采用HashList,接近O(1)的读取插入时间复杂度。这里的HashList采用单向链表,其中next_hash为单向链表的指针,hash为hash值,取余数组长度后的下标,里面存放单向链表指针。 table_指针指向为何HashList的数组的大小为2的整数倍?因为取余操作只需要&(2的整数倍-1)就行了,高效率。但是带来的问题是Hash后的数据可能会不够均匀。有的实现采用递增的大质数数组,在扩容时直接跳到下一个大质数,作为数组的大小,优点为取余后会比较均匀。为实现LRU,必然要把最近访问的链表项放到链表的头部,所以除了HashList表里面的指针外,还要有一个LRU链表的指针prev和next。使用 lru_指针指向lru_为冷数据,in_use_为热数据ShardedLRUCache 为一个大小为16的分片cache。为什么要分片?锁的粒度更小,读写更快。如果只用一个LRUCache加锁是全局的,期间都会卡住。为何要有引用计数,参考:http://www.jianshu.com/p/9e7773432772读取数据时,用户首先从缓存中查找欲读的数据是否存在,如果存在,用户将获得命中缓存的Handle。在用户持有该Handle的期间,该缓存可能被删除(多种原因,如:超过缓存容量触发回收、具有相同key的新缓存插入、整个缓存被析构等),导致用户访问到非法内存,程序崩溃。因此,需要使用引用计数来维护Handle的生命周期。 e->refs 为0,则删除,没有人用了,deallocate释放空间e->refs大于0,则是在LRU链表中http://www.cnblogs.com/liuhao/archive/2012/11/29/2795455.htmlhttps://www.2cto.com/database/201508/439350.htmlhttps://yq.aliyun.com/articles/2746http://blog.csdn.net/u011693064/article/details/77485631*/namespace leveldb{Cache::~Cache(){}namespace {// LRU cache implementation//// Cache entries have an "in_cache" boolean indicating whether the cache has a// reference on the entry.  The only ways that this can become false without the// entry being passed to its "deleter" are via Erase(), via Insert() when// an element with a duplicate key is inserted, or on destruction of the cache.//// The cache keeps two linked lists of items in the cache.  All items in the// cache are in one list or the other, and never both.  Items still referenced// by clients but erased from the cache are in neither list.  The lists are:// - in-use:  contains the items currently referenced by clients, in no//   particular order.  (This list is used for invariant checking.  If we//   removed the check, elements that would otherwise be on this list could be//   left as disconnected singleton lists.)// - LRU:  contains the items not currently referenced by clients, in LRU order// Elements are moved between these lists by the Ref() and Unref() methods,// when they detect an element in the cache acquiring or losing its only// external reference.// An entry is a variable length heap-allocated structure.  Entries// are kept in a circular doubly linked list ordered by access time.struct LRUHandle {void* value;void (*deleter)(const Slice&, void* value);LRUHandle* next_hash;LRUHandlenext;LRUHandleprev;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 comparisonschar key_data[1];   // Beginning of keySlice key() const {// For cheaper lookups, we allow a temporary Handle object// to store a pointer to a key in "value".if ( next == this ){return *(reinterpret_cast<Slice*>(value));} else{return Slice( key_data, key_length );}}};// We provide our own simple hash table since it removes a whole bunch// of porting hacks and is also faster than some of the built-in hash// table implementations in some of the compiler/runtime combinations// we have tested.  E.g., readrandom speeds up by ~5% over the g++// 4.4.3's builtin hashtable.class HandleTable {public:HandleTable() : length_(0), elems_(0), list_(NULL) { Resize(); }~HandleTable(){ delete[] list_;}LRUHandleLookupconst Slicekeyuint32_t hash ) {return *FindPointer( keyhash );}LRUHandleInsertLRUHandle* h ){LRUHandle** ptr = FindPointer( h->key(), h->hash );LRUHandle* old = *ptr;h->next_hash = (old == NULL ? NULL : old->next_hash);*ptr = h;if ( old == NULL ) {++elems_;if ( elems_ > length_ ) {// Since each cache entry is fairly large, we aim for a small// average linked list length (<= 1).Resize();}}return old;}// 从HashList的单向链表中删除,使用双指针删除的技巧,*result 是前一项的next指针//result为key相等的那一项LRUHandle* Remove( const Slicekeyuint32_t hash ) {LRUHandle** ptr = FindPointer( keyhash );LRUHandleresult = *ptr;if ( result != NULL ) {*ptr = result->next_hash;--elems_;}return result;}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_;// Return a pointer to slot that points to a cache entry that// matches key/hash.  If there is no such cache entry, return a// pointer to the trailing slot in the corresponding linked list.// 在hash值为输入参数hash的链表中搜索key为输入参数key的项,找到则返回// 下一项使用next_hash指向,里面的项都是 LRUHandle* 的指针,指向指针的指针LRUHandle** FindPointer( const Slicekeyuint32_t hash ){LRUHandle** ptr = &list_[ hash & (length_ - 1) ];while ( *ptr != NULL &&((*ptr)->hash != hash || key != (*ptr)->key() ) ) {ptr = &(*ptr)->next_hash;}return ptr;}// rehash 操作,扩容两倍(如需),同步把原来的所有项,挂接到新链表void Resize() {uint32_t new_length = 4;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;// 将list_数组中的所有项,都insert到扩容后的链表new_list中,2的整数倍for ( uint32_t i = 0; i < length_; i++ ) {LRUHandle* h = list_[i];while ( h != NULL ) {LRUHandlenext = 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;}};// A single shard of sharded cache.class LRUCache{public:LRUCache();~LRUCache();// Separate from constructor so caller can easily make an array of LRUCachevoid SetCapacity( size_t capacity ) { capacity_ = capacity; }// Like Cache methods, but with an extra "hash" parameter.Cache::HandleInsertconst Slicekeyuint32_t hash,void* value, size_t charge,void (*deleter)(const Slicekeyvoid* value) );Cache::HandleLookupconst Slicekeyuint32_t hash );void ReleaseCache::Handle* handle );void Eraseconst Slicekeyuint32_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);// Initialized before use.size_t capacity_;// mutex_ protects the following state.mutable port::Mutex mutex_;size_t usage_;// Dummy head of LRU list.// lru.prev is newest entry, lru.next is oldest entry.// Entries have refs==1 and in_cache==true.LRUHandle lru_;// Dummy head of in-use list.// Entries are in use by clients, and have refs >= 2 and in_cache==true.LRUHandle in_use_;HandleTable table_;};// 初始化为空的循环双向链表,lru_、in_use_的next和prev都指向自己LRUCache::LRUCache(): 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_ );  // Error if caller has an unreleased handlefor ( LRUHandle* e = lru_.next; e != &lru_; ){LRUHandlenext = e->next;assert( e->in_cache );e->in_cache = false;assert( e->refs == 1 );  // Invariant of lru_ list.Unref( e );e = next;}}// 使用一次,就要放在LRU表的表头位置,last recent used void LRUCache::RefLRUHandle* e ) {// 已经在LRU链表中了,则放在正在使用链表的头部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++;}void LRUCache::UnrefLRUHandle* 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.// 如果不再使用了,但是还有人在ref指向着,则放到lru_链表的头部LRU_Remove( e );LRU_Append( &lru_, e );}}// 将e从prev和next指针链接的链表中拆下来void LRUCache::LRU_RemoveLRUHandle* e ) {e->next->prev = e->prev;e->prev->next = e->next;}// 将e插入到list指向的双向循环链表的头部元素list的前面void LRUCache::LRU_AppendLRUHandle* list, LRUHandle* e ){// Make "e" newest entry by inserting just before *liste->next = list;e->prev = list->prev;e->prev->next = e;e->next->prev = e;}// Lookup函数调用,会增加RefCache::HandleLRUCache::Lookupconst Slicekeyuint32_t hash ){MutexLock l( &mutex_ );LRUHandle* e = table_.Lookupkeyhash );if ( e != NULL ) {Ref( e );}return reinterpret_cast<Cache::Handle*>( e );}// Release会释放refvoid LRUCache::ReleaseCache::Handle* handle ) {MutexLock l( &mutex_ );Unrefreinterpret_cast<LRUHandle*>( handle ) );}Cache::HandleLRUCache::Insert(const Slicekeyuint32_t hashvoid* value, size_t charge,void (*deleter)( const Slicekeyvoid* value ) ){MutexLock l( &mutex_ );// 创建一个LRUHandle,初始化 in_cache为falseLRUHandle* e = reinterpret_cast<LRUHandle*>(mallocsizeof ( 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;  // for the returned handle.memcpy( e->key_data, key.data(), key.size() );if ( capacity_ > 0 ) {e->refs++;  // for the cache's reference.e->in_cache = true;LRU_Append( &in_use_, e );usage_ += charge;FinishErase( table_.Insert( e ) );} // else don't cache.  (Tests use capacity_==0 to turn off caching.)while ( usage_ > capacity_ && lru_.next != &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 NDEBUGassert( erased );}}return reinterpret_cast<Cache::Handle*>( e );}// If e != NULL, finish removing *e from the cache; it has already been removed// from the hash table.  Return whether e != NULL.  Requires mutex_ held.bool LRUCache::FinishEraseLRUHandle* e ){if ( e != NULL ) {assert( e->in_cache );LRU_Remove( e );e->in_cache = false;usage_ -= e->charge;Unref( e );}return e != NULL;}// 先从table_表中删除,然后从lru_链表中删除,如果还有人ref着,则放入lru_链表的头部位置void LRUCache::Eraseconst Slicekeyuint32_t hash ){MutexLock l( &mutex_ );FinishErase( table_.Remove( keyhash ) );}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 NDEBUGassert( erased );}}}static const int kNumShardBits = 4;static const int kNumShards = 1 << kNumShardBits;//16class ShardedLRUCache : public Cache{private:// 16个LRUCacheLRUCache 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 ShardedLRUCachesize_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 HandleInsertconst Slicekeyvoid* value, size_t charge,void (*deleter)(const Slicekeyvoid* value) ) {const uint32_t hash = HashSlice( key );return shard_[ Shard( hash ) ].Insertkeyhash, value, charge, deleter );}virtual HandleLookupconst Slicekey ){const uint32_t hash = HashSlice( key );return shard_[ Shard( hash ) ].Lookupkeyhash );}virtual void ReleaseHandle* handle ) {LRUHandle* h = reinterpret_cast<LRUHandle*>( handle );shard_[ Shard( h->hash ) ].Release( handle );}virtual void Eraseconst Slicekey ) {const uint32_t hash = HashSlice( key );shard_[ Shard( hash ) ].Erasekeyhash );}virtual voidValueHandle* handle ){return reinterpret_cast<LRUHandle*>( handle )->value;}virtual uint64_t NewId() {MutexLock l( &id_mutex_ );return ++(last_id_);}virtual void Prune() {for ( int s = 0; s < kNumShards; s++ ) {shard_[ s ].Prune();}}virtual size_t TotalCharge() const{size_t total = 0;for ( int s = 0; s < kNumShards; s++ ){total += shard_[ s ].TotalCharge();}return total;}};}  // end anonymous namespace// 不想暴露内部的ShardedLRUCache,方便将来升级替换,对外只提供返回一个Cache*的接口//将来升级对外部调用透明, API不变,内部实现变了CacheNewLRUCachesize_t capacity ){return new ShardedLRUCache( capacity );}}  // namespace leveldb