2011-03-19 06:37:00 +08:00
|
|
|
// 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.
|
|
|
|
|
|
|
|
#if defined(LEVELDB_PLATFORM_POSIX) || defined(LEVELDB_PLATFORM_ANDROID)
|
|
|
|
#include <unordered_set>
|
|
|
|
#elif defined(LEVELDB_PLATFORM_CHROMIUM)
|
|
|
|
#include "base/hash_tables.h"
|
|
|
|
#else
|
|
|
|
#include <hash_set> // TODO(sanjay): Switch to unordered_set when possible.
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
2011-03-31 02:35:40 +08:00
|
|
|
#include "leveldb/cache.h"
|
2011-03-19 06:37:00 +08:00
|
|
|
#include "port/port.h"
|
|
|
|
#include "util/hash.h"
|
|
|
|
#include "util/mutexlock.h"
|
|
|
|
|
|
|
|
namespace leveldb {
|
|
|
|
|
|
|
|
Cache::~Cache() {
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
// LRU cache implementation
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
LRUHandle* prev;
|
|
|
|
size_t charge; // TODO(opt): Only allow uint32_t?
|
|
|
|
size_t key_length;
|
|
|
|
size_t refs; // TODO(opt): Pack with "key_length"?
|
|
|
|
char key_data[1]; // Beginning of key
|
|
|
|
|
|
|
|
Slice 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Pick a platform specific hash_set instantiation
|
|
|
|
#if defined(LEVELDB_PLATFORM_CHROMIUM) && defined(OS_WIN)
|
|
|
|
// Microsoft's hash_set deviates from the standard. See
|
|
|
|
// http://msdn.microsoft.com/en-us/library/1t4xas78(v=vs.80).aspx
|
|
|
|
// for details. Basically the 2 param () operator is a less than and
|
|
|
|
// the 1 param () operator is a hash function.
|
|
|
|
struct HandleHashCompare : public stdext::hash_compare<LRUHandle*> {
|
|
|
|
size_t operator() (LRUHandle* h) const {
|
|
|
|
Slice k = h->key();
|
|
|
|
return Hash(k.data(), k.size(), 0);
|
|
|
|
}
|
|
|
|
bool operator() (LRUHandle* a, LRUHandle* b) const {
|
|
|
|
return a->key().compare(b->key()) < 0;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
typedef base::hash_set<LRUHandle*, HandleHashCompare> HandleTable;
|
|
|
|
#else
|
|
|
|
struct HandleHash {
|
|
|
|
inline size_t operator()(LRUHandle* h) const {
|
|
|
|
Slice k = h->key();
|
|
|
|
return Hash(k.data(), k.size(), 0);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct HandleEq {
|
|
|
|
inline bool operator()(LRUHandle* a, LRUHandle* b) const {
|
|
|
|
return a->key() == b->key();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
# if defined(LEVELDB_PLATFORM_CHROMIUM)
|
|
|
|
typedef base::hash_set<LRUHandle*, HandleHash, HandleEq> HandleTable;
|
|
|
|
# elif defined(LEVELDB_PLATFORM_POSIX) || defined(LEVELDB_PLATFORM_ANDROID)
|
|
|
|
typedef std::unordered_set<LRUHandle*, HandleHash, HandleEq> HandleTable;
|
|
|
|
# else
|
|
|
|
typedef __gnu_cxx::hash_set<LRUHandle*, HandleHash, HandleEq> HandleTable;
|
|
|
|
# endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
class LRUCache : public Cache {
|
|
|
|
public:
|
|
|
|
explicit LRUCache(size_t capacity);
|
|
|
|
virtual ~LRUCache();
|
|
|
|
|
|
|
|
virtual Handle* Insert(const Slice& key, void* value, size_t charge,
|
|
|
|
void (*deleter)(const Slice& key, void* value));
|
|
|
|
virtual Handle* Lookup(const Slice& key);
|
|
|
|
virtual void Release(Handle* handle);
|
|
|
|
virtual void* Value(Handle* handle);
|
|
|
|
virtual void Erase(const Slice& key);
|
|
|
|
virtual uint64_t NewId();
|
|
|
|
|
|
|
|
private:
|
|
|
|
void LRU_Remove(LRUHandle* e);
|
|
|
|
void LRU_Append(LRUHandle* e);
|
|
|
|
void Unref(LRUHandle* e);
|
|
|
|
|
|
|
|
// Constructor parameters
|
|
|
|
const size_t capacity_;
|
|
|
|
|
|
|
|
// mutex_ protects the following state.
|
|
|
|
port::Mutex mutex_;
|
|
|
|
size_t usage_;
|
|
|
|
uint64_t last_id_;
|
|
|
|
|
|
|
|
// Dummy head of LRU list.
|
|
|
|
// lru.prev is newest entry, lru.next is oldest entry.
|
|
|
|
LRUHandle lru_;
|
|
|
|
|
|
|
|
HandleTable table_;
|
|
|
|
};
|
|
|
|
|
|
|
|
LRUCache::LRUCache(size_t capacity)
|
|
|
|
: capacity_(capacity),
|
|
|
|
usage_(0),
|
|
|
|
last_id_(0) {
|
|
|
|
// Make empty circular linked list
|
|
|
|
lru_.next = &lru_;
|
|
|
|
lru_.prev = &lru_;
|
|
|
|
}
|
|
|
|
|
|
|
|
LRUCache::~LRUCache() {
|
|
|
|
table_.clear();
|
|
|
|
for (LRUHandle* e = lru_.next; e != &lru_; ) {
|
|
|
|
LRUHandle* next = e->next;
|
|
|
|
assert(e->refs == 1); // Error if caller has an unreleased handle
|
|
|
|
Unref(e);
|
|
|
|
e = next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::Unref(LRUHandle* e) {
|
|
|
|
assert(e->refs > 0);
|
|
|
|
e->refs--;
|
|
|
|
if (e->refs <= 0) {
|
|
|
|
usage_ -= e->charge;
|
|
|
|
(*e->deleter)(e->key(), e->value);
|
|
|
|
free(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::LRU_Remove(LRUHandle* e) {
|
|
|
|
e->next->prev = e->prev;
|
|
|
|
e->prev->next = e->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::LRU_Append(LRUHandle* e) {
|
|
|
|
// Make "e" newest entry by inserting just before lru_
|
|
|
|
e->next = &lru_;
|
|
|
|
e->prev = lru_.prev;
|
|
|
|
e->prev->next = e;
|
|
|
|
e->next->prev = e;
|
|
|
|
}
|
|
|
|
|
|
|
|
Cache::Handle* LRUCache::Lookup(const Slice& key) {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
|
|
|
|
LRUHandle dummy;
|
|
|
|
dummy.next = &dummy;
|
|
|
|
dummy.value = const_cast<Slice*>(&key);
|
|
|
|
HandleTable::iterator iter = table_.find(&dummy);
|
|
|
|
if (iter == table_.end()) {
|
|
|
|
return NULL;
|
|
|
|
} else {
|
|
|
|
LRUHandle* e = const_cast<LRUHandle*>(*iter);
|
|
|
|
e->refs++;
|
|
|
|
LRU_Remove(e);
|
|
|
|
LRU_Append(e);
|
|
|
|
return reinterpret_cast<Handle*>(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void* LRUCache::Value(Handle* handle) {
|
|
|
|
return reinterpret_cast<LRUHandle*>(handle)->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::Release(Handle* handle) {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
Unref(reinterpret_cast<LRUHandle*>(handle));
|
|
|
|
}
|
|
|
|
|
|
|
|
Cache::Handle* LRUCache::Insert(const Slice& key, 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->refs = 2; // One from LRUCache, one for the returned handle
|
|
|
|
memcpy(e->key_data, key.data(), key.size());
|
|
|
|
LRU_Append(e);
|
|
|
|
usage_ += charge;
|
|
|
|
|
|
|
|
std::pair<HandleTable::iterator,bool> p = table_.insert(e);
|
|
|
|
if (!p.second) {
|
|
|
|
// Kill existing entry
|
|
|
|
LRUHandle* old = const_cast<LRUHandle*>(*(p.first));
|
|
|
|
LRU_Remove(old);
|
|
|
|
table_.erase(p.first);
|
|
|
|
table_.insert(e);
|
|
|
|
Unref(old);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (usage_ > capacity_ && lru_.next != &lru_) {
|
|
|
|
LRUHandle* old = lru_.next;
|
|
|
|
LRU_Remove(old);
|
|
|
|
table_.erase(old);
|
|
|
|
Unref(old);
|
|
|
|
}
|
|
|
|
|
|
|
|
return reinterpret_cast<Handle*>(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
void LRUCache::Erase(const Slice& key) {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
|
|
|
|
LRUHandle dummy;
|
|
|
|
dummy.next = &dummy;
|
|
|
|
dummy.value = const_cast<Slice*>(&key);
|
|
|
|
HandleTable::iterator iter = table_.find(&dummy);
|
|
|
|
if (iter != table_.end()) {
|
|
|
|
LRUHandle* e = const_cast<LRUHandle*>(*iter);
|
|
|
|
LRU_Remove(e);
|
|
|
|
table_.erase(iter);
|
|
|
|
Unref(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint64_t LRUCache::NewId() {
|
|
|
|
MutexLock l(&mutex_);
|
|
|
|
return ++(last_id_);
|
|
|
|
}
|
|
|
|
|
|
|
|
} // end anonymous namespace
|
|
|
|
|
|
|
|
Cache* NewLRUCache(size_t capacity) {
|
|
|
|
return new LRUCache(capacity);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|