Summary: This patch adds three new MemTableRep's: UnsortedRep, PrefixHashRep, and VectorRep. UnsortedRep stores keys in an std::unordered_map of std::sets. When an iterator is requested, it dumps the keys into an std::set and iterates over that. VectorRep stores keys in an std::vector. When an iterator is requested, it creates a copy of the vector and sorts it using std::sort. The iterator accesses that new vector. PrefixHashRep stores keys in an unordered_map mapping prefixes to ordered sets. I also added one API change. I added a function MemTableRep::MarkImmutable. This function is called when the rep is added to the immutable list. It doesn't do anything yet, but it seems like that could be useful. In particular, for the vectorrep, it means we could elide the extra copy and just sort in place. The only reason I haven't done that yet is because the use of the ArenaAllocator complicates things (I can elaborate on this if needed). Test Plan: make -j32 check ./db_stress --memtablerep=vector ./db_stress --memtablerep=unsorted ./db_stress --memtablerep=prefixhash --prefix_size=10 Reviewers: dhruba, haobo, emayanke Reviewed By: dhruba CC: leveldb Differential Revision: https://reviews.facebook.net/D12117main
parent
17dc128048
commit
74781a0c49
@ -0,0 +1,49 @@ |
||||
#ifndef LEVELDB_UTIL_STL_WRAPPERS_H_ |
||||
#define LEVELDB_UTIL_STL_WRAPPERS_H_ |
||||
|
||||
#include "util/murmurhash.h" |
||||
#include "util/coding.h" |
||||
|
||||
#include "leveldb/memtablerep.h" |
||||
#include "leveldb/slice.h" |
||||
|
||||
namespace leveldb { |
||||
namespace stl_wrappers { |
||||
class Base { |
||||
protected: |
||||
const MemTableRep::KeyComparator& compare_; |
||||
explicit Base(const MemTableRep::KeyComparator& compare) |
||||
: compare_(compare) { } |
||||
}; |
||||
|
||||
struct Compare : private Base { |
||||
explicit Compare(const MemTableRep::KeyComparator& compare) |
||||
: Base(compare) { } |
||||
inline bool operator()(const char* a, const char* b) const { |
||||
return compare_(a, b) < 0; |
||||
} |
||||
}; |
||||
|
||||
struct Hash { |
||||
inline size_t operator()(const char* buf) const { |
||||
Slice internal_key = GetLengthPrefixedSlice(buf); |
||||
Slice value = |
||||
GetLengthPrefixedSlice(internal_key.data() + internal_key.size()); |
||||
unsigned int hval = MurmurHash(internal_key.data(), internal_key.size(), |
||||
0); |
||||
hval = MurmurHash(value.data(), value.size(), hval); |
||||
return hval; |
||||
} |
||||
}; |
||||
|
||||
struct KeyEqual : private Base { |
||||
explicit KeyEqual(const MemTableRep::KeyComparator& compare) |
||||
: Base(compare) { } |
||||
inline bool operator()(const char* a, const char* b) const { |
||||
return this->compare_(a, b) == 0; |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
#endif // LEVELDB_UTIL_STL_WRAPPERS_H_
|
@ -0,0 +1,336 @@ |
||||
#include <unordered_map> |
||||
#include <set> |
||||
#include <vector> |
||||
#include <algorithm> |
||||
#include <iostream> |
||||
|
||||
#include "leveldb/memtablerep.h" |
||||
#include "leveldb/arena.h" |
||||
#include "leveldb/slice.h" |
||||
#include "leveldb/slice_transform.h" |
||||
#include "port/port.h" |
||||
#include "util/mutexlock.h" |
||||
#include "util/murmurhash.h" |
||||
#include "util/stl_wrappers.h" |
||||
|
||||
namespace std { |
||||
template <> |
||||
struct hash<leveldb::Slice> { |
||||
size_t operator()(const leveldb::Slice& slice) const { |
||||
return MurmurHash(slice.data(), slice.size(), 0); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
namespace leveldb { |
||||
namespace { |
||||
|
||||
using namespace stl_wrappers; |
||||
|
||||
class TransformRep : public MemTableRep { |
||||
public: |
||||
TransformRep(const KeyComparator& compare, Arena* arena, |
||||
const SliceTransform* transform, size_t bucket_size, |
||||
size_t num_locks); |
||||
|
||||
virtual void Insert(const char* key) override; |
||||
|
||||
virtual bool Contains(const char* key) const override; |
||||
|
||||
virtual size_t ApproximateMemoryUsage() override; |
||||
|
||||
virtual ~TransformRep() { } |
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator() override; |
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator( |
||||
const Slice& slice) override; |
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> GetTransformIterator( |
||||
const Slice& transformed); |
||||
|
||||
private: |
||||
typedef std::set<const char*, Compare> Bucket; |
||||
typedef std::unordered_map<Slice, std::shared_ptr<Bucket>> BucketMap; |
||||
|
||||
// Maps slices (which are transformed user keys) to buckets of keys sharing
|
||||
// the same transform.
|
||||
BucketMap buckets_; |
||||
|
||||
// rwlock_ protects access to the buckets_ data structure itself. Each bucket
|
||||
// has its own read-write lock as well.
|
||||
mutable port::RWMutex rwlock_; |
||||
|
||||
// Keep track of approximately how much memory is being used.
|
||||
size_t memory_usage_ = 0; |
||||
|
||||
// The user-supplied transform whose domain is the user keys.
|
||||
const SliceTransform* transform_; |
||||
|
||||
// Get a bucket from buckets_. If the bucket hasn't been initialized yet,
|
||||
// initialize it before returning. Must be externally synchronized.
|
||||
std::shared_ptr<Bucket>& GetBucket(const Slice& transformed); |
||||
|
||||
port::RWMutex* GetLock(const Slice& transformed) const; |
||||
|
||||
mutable std::vector<port::RWMutex> locks_; |
||||
|
||||
const KeyComparator& compare_; |
||||
|
||||
class Iterator : public MemTableRep::Iterator { |
||||
public: |
||||
explicit Iterator(std::shared_ptr<Bucket> items); |
||||
|
||||
virtual ~Iterator() { }; |
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
virtual bool Valid() const; |
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
virtual const char* key() const; |
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next(); |
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev(); |
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
virtual void Seek(const char* target); |
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToFirst(); |
||||
|
||||
// Position at the last entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToLast(); |
||||
private: |
||||
std::shared_ptr<Bucket> items_; |
||||
Bucket::const_iterator cit_; |
||||
}; |
||||
|
||||
class EmptyIterator : public MemTableRep::Iterator { |
||||
// This is used when there wasn't a bucket. It is cheaper than
|
||||
// instantiating an empty bucket over which to iterate.
|
||||
public: |
||||
virtual bool Valid() const { |
||||
return false; |
||||
} |
||||
virtual const char* key() const { |
||||
assert(false); |
||||
return nullptr; |
||||
} |
||||
virtual void Next() { } |
||||
virtual void Prev() { } |
||||
virtual void Seek(const char* target) { } |
||||
virtual void SeekToFirst() { } |
||||
virtual void SeekToLast() { } |
||||
static std::shared_ptr<EmptyIterator> GetInstance(); |
||||
private: |
||||
static std::shared_ptr<EmptyIterator> instance; |
||||
EmptyIterator() { } |
||||
}; |
||||
|
||||
class TransformIterator : public Iterator { |
||||
public: |
||||
explicit TransformIterator(std::shared_ptr<Bucket> items, |
||||
port::RWMutex* rwlock); |
||||
virtual ~TransformIterator() { } |
||||
private: |
||||
const ReadLock l_; |
||||
}; |
||||
}; |
||||
|
||||
class PrefixHashRep : public TransformRep { |
||||
public: |
||||
PrefixHashRep(const KeyComparator& compare, Arena* arena, |
||||
const SliceTransform* transform, size_t bucket_size, |
||||
size_t num_locks) : TransformRep(compare, arena, transform, |
||||
bucket_size, num_locks) { } |
||||
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetPrefixIterator( |
||||
const Slice& prefix) override; |
||||
}; |
||||
|
||||
std::shared_ptr<TransformRep::Bucket>& TransformRep::GetBucket( |
||||
const Slice& transformed) { |
||||
WriteLock l(&rwlock_); |
||||
auto& bucket = buckets_[transformed]; |
||||
if (!bucket) { |
||||
bucket.reset( |
||||
new decltype(buckets_)::mapped_type::element_type(Compare(compare_))); |
||||
// To memory_usage_ we add the size of the std::set and the size of the
|
||||
// std::pair (decltype(buckets_)::value_type) which includes the
|
||||
// Slice and the std::shared_ptr
|
||||
memory_usage_ += sizeof(*bucket) + |
||||
sizeof(decltype(buckets_)::value_type); |
||||
} |
||||
return bucket; |
||||
} |
||||
|
||||
port::RWMutex* TransformRep::GetLock(const Slice& transformed) const { |
||||
return &locks_[std::hash<Slice>()(transformed) % locks_.size()]; |
||||
} |
||||
|
||||
TransformRep::TransformRep(const KeyComparator& compare, Arena* arena, |
||||
const SliceTransform* transform, size_t bucket_size, |
||||
size_t num_locks) |
||||
: buckets_(bucket_size), |
||||
transform_(transform), |
||||
locks_(num_locks), |
||||
compare_(compare) { } |
||||
|
||||
void TransformRep::Insert(const char* key) { |
||||
assert(!Contains(key)); |
||||
auto transformed = transform_->Transform(UserKey(key)); |
||||
auto& bucket = GetBucket(transformed); |
||||
WriteLock bl(GetLock(transformed)); |
||||
bucket->insert(key); |
||||
memory_usage_ += sizeof(key); |
||||
} |
||||
|
||||
bool TransformRep::Contains(const char* key) const { |
||||
ReadLock l(&rwlock_); |
||||
auto transformed = transform_->Transform(UserKey(key)); |
||||
auto bucket = buckets_.find(transformed); |
||||
if (bucket == buckets_.end()) { |
||||
return false; |
||||
} |
||||
ReadLock bl(GetLock(transformed)); |
||||
return bucket->second->count(key) != 0; |
||||
} |
||||
|
||||
size_t TransformRep::ApproximateMemoryUsage() { |
||||
return memory_usage_; |
||||
} |
||||
|
||||
std::shared_ptr<TransformRep::EmptyIterator> |
||||
TransformRep::EmptyIterator::GetInstance() { |
||||
if (!instance) { |
||||
instance.reset(new TransformRep::EmptyIterator); |
||||
} |
||||
return instance; |
||||
} |
||||
|
||||
TransformRep::Iterator::Iterator(std::shared_ptr<Bucket> items) |
||||
: items_(items), |
||||
cit_(items_->begin()) { } |
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
bool TransformRep::Iterator::Valid() const { |
||||
return cit_ != items_->end(); |
||||
} |
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
const char* TransformRep::Iterator::key() const { |
||||
assert(Valid()); |
||||
return *cit_; |
||||
} |
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
void TransformRep::Iterator::Next() { |
||||
assert(Valid()); |
||||
if (cit_ == items_->end()) { |
||||
return; |
||||
} |
||||
++cit_; |
||||
} |
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
void TransformRep::Iterator::Prev() { |
||||
assert(Valid()); |
||||
if (cit_ == items_->begin()) { |
||||
// If you try to go back from the first element, the iterator should be
|
||||
// invalidated. So we set it to past-the-end. This means that you can
|
||||
// treat the container circularly.
|
||||
cit_ = items_->end(); |
||||
} else { |
||||
--cit_; |
||||
} |
||||
} |
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
void TransformRep::Iterator::Seek(const char* target) { |
||||
cit_ = items_->lower_bound(target); |
||||
} |
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
void TransformRep::Iterator::SeekToFirst() { |
||||
cit_ = items_->begin(); |
||||
} |
||||
|
||||
void TransformRep::Iterator::SeekToLast() { |
||||
cit_ = items_->end(); |
||||
if (items_->size() != 0) { |
||||
--cit_; |
||||
} |
||||
} |
||||
|
||||
TransformRep::TransformIterator::TransformIterator( |
||||
std::shared_ptr<Bucket> items, port::RWMutex* rwlock) |
||||
: Iterator(items), l_(rwlock) { } |
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> TransformRep::GetIterator() { |
||||
auto items = std::make_shared<Bucket>(Compare(compare_)); |
||||
// Hold read locks on all locks
|
||||
ReadLock l(&rwlock_); |
||||
std::for_each(locks_.begin(), locks_.end(), [] (port::RWMutex& lock) { |
||||
lock.ReadLock(); |
||||
}); |
||||
for (auto& bucket : buckets_) { |
||||
items->insert(bucket.second->begin(), bucket.second->end()); |
||||
} |
||||
std::for_each(locks_.begin(), locks_.end(), [] (port::RWMutex& lock) { |
||||
lock.Unlock(); |
||||
}); |
||||
return std::make_shared<Iterator>(std::move(items)); |
||||
} |
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> TransformRep::GetTransformIterator( |
||||
const Slice& transformed) { |
||||
ReadLock l(&rwlock_); |
||||
auto bucket = buckets_.find(transformed); |
||||
if (bucket == buckets_.end()) { |
||||
return EmptyIterator::GetInstance(); |
||||
} |
||||
return std::make_shared<TransformIterator>(bucket->second, |
||||
GetLock(transformed)); |
||||
} |
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> TransformRep::GetIterator( |
||||
const Slice& slice) { |
||||
auto transformed = transform_->Transform(slice); |
||||
return GetTransformIterator(transformed); |
||||
} |
||||
|
||||
std::shared_ptr<TransformRep::EmptyIterator> |
||||
TransformRep::EmptyIterator::instance; |
||||
|
||||
} // anon namespace
|
||||
|
||||
std::shared_ptr<MemTableRep> TransformRepFactory::CreateMemTableRep( |
||||
MemTableRep::KeyComparator& compare, Arena* arena) { |
||||
return std::make_shared<TransformRep>(compare, arena, transform_, |
||||
bucket_count_, num_locks_); |
||||
} |
||||
|
||||
std::shared_ptr<MemTableRep> PrefixHashRepFactory::CreateMemTableRep( |
||||
MemTableRep::KeyComparator& compare, Arena* arena) { |
||||
return std::make_shared<PrefixHashRep>(compare, arena, transform_, |
||||
bucket_count_, num_locks_); |
||||
} |
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> PrefixHashRep::GetPrefixIterator( |
||||
const Slice& prefix) { |
||||
return TransformRep::GetTransformIterator(prefix); |
||||
} |
||||
|
||||
} // namespace leveldb
|
@ -0,0 +1,215 @@ |
||||
#include "leveldb/memtablerep.h" |
||||
|
||||
#include <unordered_set> |
||||
#include <set> |
||||
#include <memory> |
||||
#include <algorithm> |
||||
#include <type_traits> |
||||
|
||||
#include "leveldb/arena.h" |
||||
#include "port/port.h" |
||||
#include "util/mutexlock.h" |
||||
#include "util/stl_wrappers.h" |
||||
|
||||
namespace leveldb { |
||||
namespace { |
||||
|
||||
using namespace stl_wrappers; |
||||
|
||||
class VectorRep : public MemTableRep { |
||||
public: |
||||
VectorRep(const KeyComparator& compare, Arena* arena, size_t count); |
||||
|
||||
// Insert key into the collection. (The caller will pack key and value into a
|
||||
// single buffer and pass that in as the parameter to Insert)
|
||||
// REQUIRES: nothing that compares equal to key is currently in the
|
||||
// collection.
|
||||
virtual void Insert(const char* key) override; |
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
virtual bool Contains(const char* key) const override; |
||||
|
||||
virtual void MarkReadOnly() override; |
||||
|
||||
virtual size_t ApproximateMemoryUsage() override; |
||||
|
||||
virtual ~VectorRep() override { } |
||||
|
||||
class Iterator : public MemTableRep::Iterator { |
||||
std::shared_ptr<std::vector<const char*>> bucket_; |
||||
typename std::vector<const char*>::const_iterator cit_; |
||||
const KeyComparator& compare_; |
||||
public: |
||||
explicit Iterator(std::shared_ptr<std::vector<const char*>> bucket, |
||||
const KeyComparator& compare); |
||||
|
||||
// Initialize an iterator over the specified collection.
|
||||
// The returned iterator is not valid.
|
||||
// explicit Iterator(const MemTableRep* collection);
|
||||
virtual ~Iterator() override { }; |
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
virtual bool Valid() const override; |
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
virtual const char* key() const override; |
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Next() override; |
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
virtual void Prev() override; |
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
virtual void Seek(const char* target) override; |
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToFirst() override; |
||||
|
||||
// Position at the last entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
virtual void SeekToLast() override; |
||||
}; |
||||
|
||||
// Unhide default implementations of GetIterator()
|
||||
using MemTableRep::GetIterator; |
||||
|
||||
// Return an iterator over the keys in this representation.
|
||||
virtual std::shared_ptr<MemTableRep::Iterator> GetIterator() override; |
||||
|
||||
private: |
||||
typedef std::vector<const char*> Bucket; |
||||
std::shared_ptr<Bucket> bucket_; |
||||
mutable port::RWMutex rwlock_; |
||||
bool immutable_ = false; |
||||
bool sorted_ = false; |
||||
const KeyComparator& compare_; |
||||
}; |
||||
|
||||
void VectorRep::Insert(const char* key) { |
||||
assert(!Contains(key)); |
||||
WriteLock l(&rwlock_); |
||||
assert(!immutable_); |
||||
bucket_->push_back(key); |
||||
} |
||||
|
||||
// Returns true iff an entry that compares equal to key is in the collection.
|
||||
bool VectorRep::Contains(const char* key) const { |
||||
ReadLock l(&rwlock_); |
||||
return std::find(bucket_->begin(), bucket_->end(), key) != bucket_->end(); |
||||
} |
||||
|
||||
void VectorRep::MarkReadOnly() { |
||||
WriteLock l(&rwlock_); |
||||
immutable_ = true; |
||||
} |
||||
|
||||
size_t VectorRep::ApproximateMemoryUsage() { |
||||
return |
||||
sizeof(bucket_) + sizeof(*bucket_) + |
||||
bucket_->size() * |
||||
sizeof( |
||||
std::remove_reference<decltype(*bucket_)>::type::value_type |
||||
); |
||||
} |
||||
|
||||
VectorRep::VectorRep(const KeyComparator& compare, Arena* arena, size_t count) |
||||
: bucket_(new Bucket(count)), |
||||
compare_(compare) { } |
||||
|
||||
VectorRep::Iterator::Iterator(std::shared_ptr<std::vector<const char*>> bucket, |
||||
const KeyComparator& compare) |
||||
: bucket_(bucket), |
||||
cit_(bucket_->begin()), |
||||
compare_(compare) { } |
||||
|
||||
// Returns true iff the iterator is positioned at a valid node.
|
||||
bool VectorRep::Iterator::Valid() const { |
||||
return cit_ != bucket_->end(); |
||||
} |
||||
|
||||
// Returns the key at the current position.
|
||||
// REQUIRES: Valid()
|
||||
const char* VectorRep::Iterator::key() const { |
||||
assert(Valid()); |
||||
return *cit_; |
||||
} |
||||
|
||||
// Advances to the next position.
|
||||
// REQUIRES: Valid()
|
||||
void VectorRep::Iterator::Next() { |
||||
assert(Valid()); |
||||
if (cit_ == bucket_->end()) { |
||||
return; |
||||
} |
||||
++cit_; |
||||
} |
||||
|
||||
// Advances to the previous position.
|
||||
// REQUIRES: Valid()
|
||||
void VectorRep::Iterator::Prev() { |
||||
assert(Valid()); |
||||
if (cit_ == bucket_->begin()) { |
||||
// If you try to go back from the first element, the iterator should be
|
||||
// invalidated. So we set it to past-the-end. This means that you can
|
||||
// treat the container circularly.
|
||||
cit_ = bucket_->end(); |
||||
} else { |
||||
--cit_; |
||||
} |
||||
} |
||||
|
||||
// Advance to the first entry with a key >= target
|
||||
void VectorRep::Iterator::Seek(const char* target) { |
||||
// Do binary search to find first value not less than the target
|
||||
cit_ = std::equal_range(bucket_->begin(), |
||||
bucket_->end(), |
||||
target, |
||||
[this] (const char* a, const char* b) { |
||||
return compare_(a, b) < 0; |
||||
}).first; |
||||
} |
||||
|
||||
// Position at the first entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
void VectorRep::Iterator::SeekToFirst() { |
||||
cit_ = bucket_->begin(); |
||||
} |
||||
|
||||
// Position at the last entry in collection.
|
||||
// Final state of iterator is Valid() iff collection is not empty.
|
||||
void VectorRep::Iterator::SeekToLast() { |
||||
cit_ = bucket_->end(); |
||||
if (bucket_->size() != 0) { |
||||
--cit_; |
||||
} |
||||
} |
||||
|
||||
std::shared_ptr<MemTableRep::Iterator> VectorRep::GetIterator() { |
||||
std::shared_ptr<Bucket> tmp; |
||||
ReadLock l(&rwlock_); |
||||
if (immutable_) { |
||||
rwlock_.Unlock(); |
||||
rwlock_.WriteLock(); |
||||
tmp = bucket_; |
||||
if (!sorted_) { |
||||
std::sort(tmp->begin(), tmp->end(), Compare(compare_)); |
||||
sorted_ = true; |
||||
} |
||||
} else { |
||||
tmp.reset(new Bucket(*bucket_)); // make a copy
|
||||
std::sort(tmp->begin(), tmp->end(), Compare(compare_)); |
||||
} |
||||
return std::make_shared<Iterator>(tmp, compare_); |
||||
} |
||||
} // anon namespace
|
||||
|
||||
std::shared_ptr<MemTableRep> VectorRepFactory::CreateMemTableRep( |
||||
MemTableRep::KeyComparator& compare, Arena* arena) { |
||||
return std::make_shared<VectorRep>(compare, arena, count_); |
||||
} |
||||
} // namespace leveldb
|
Loading…
Reference in new issue