InlineSkipList - part 2/3

Summary:
This diff is 2/3 in a sequence that introduces a skip list optimized
for a key that is a freshly-allocated const char*.  The change is broken
into pieces to make it easier to review.  This piece removes the Key
template type, introduces the AllocateKey interface, and changes the
unit test from using uint64_t as the Key type to using pointers to an 8
byte blob.

Test Plan: unit test

Reviewers: igor, sdong

Reviewed By: sdong

Subscribers: dhruba

Differential Revision: https://reviews.facebook.net/D51285
This commit is contained in:
Nathan Bronson 2015-11-24 13:29:50 -08:00
parent 78812ec6bf
commit 5201729545
2 changed files with 118 additions and 90 deletions

View File

@ -38,7 +38,7 @@
namespace rocksdb {
template <typename Key, class Comparator>
template <class Comparator>
class InlineSkipList {
private:
struct Node;
@ -52,15 +52,18 @@ class InlineSkipList {
int32_t max_height = 12,
int32_t branching_factor = 4);
// Allocates a key that can be passed to Insert.
char* AllocateKey(size_t key_size);
// Insert key into the list.
// REQUIRES: nothing that compares equal to key is currently in the list.
void Insert(const Key& key);
void Insert(const char* key);
// Returns true iff an entry that compares equal to key is in the list.
bool Contains(const Key& key) const;
bool Contains(const char* key) const;
// Return estimated number of entries smaller than `key`.
uint64_t EstimateCount(const Key& key) const;
uint64_t EstimateCount(const char* key) const;
// Iteration over the contents of a skip list
class Iterator {
@ -79,7 +82,7 @@ class InlineSkipList {
// Returns the key at the current position.
// REQUIRES: Valid()
const Key& key() const;
const char* key() const;
// Advances to the next position.
// REQUIRES: Valid()
@ -90,7 +93,7 @@ class InlineSkipList {
void Prev();
// Advance to the first entry with a key >= target
void Seek(const Key& target);
void Seek(const char* target);
// Position at the first entry in list.
// Final state of iterator is Valid() iff list is not empty.
@ -132,22 +135,25 @@ class InlineSkipList {
return max_height_.load(std::memory_order_relaxed);
}
Node* NewNode(const Key& key, int height);
Node* NewNode(const char* key, int height);
int RandomHeight();
bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
bool Equal(const char* a, const char* b) const {
return (compare_(a, b) == 0);
}
// Return true if key is greater than the data stored in "n"
bool KeyIsAfterNode(const Key& key, Node* n) const;
bool KeyIsAfterNode(const char* key, Node* n) const;
// Returns the earliest node with a key >= key.
// Return nullptr if there is no such node.
Node* FindGreaterOrEqual(const Key& key) const;
Node* FindGreaterOrEqual(const char* key) const;
// Return the latest node with a key < key.
// Return head_ if there is no such node.
// Fills prev[level] with pointer to previous node at "level" for every
// level in [0..max_height_-1], if prev is non-null.
Node* FindLessThan(const Key& key, Node** prev = nullptr) const;
Node* FindLessThan(const char* key, Node** prev = nullptr) const;
// Return the last node in the list.
// Return head_ if list is empty.
@ -159,11 +165,11 @@ class InlineSkipList {
};
// Implementation details follow
template <typename Key, class Comparator>
struct InlineSkipList<Key, Comparator>::Node {
explicit Node(const Key& k) : key(k) {}
template <class Comparator>
struct InlineSkipList<Comparator>::Node {
explicit Node(const char* k) : key(k) {}
Key const key;
const char* const key;
// Accessors/mutators for links. Wrapped in methods so we can
// add the appropriate barriers as necessary.
@ -195,46 +201,46 @@ struct InlineSkipList<Key, Comparator>::Node {
std::atomic<Node*> next_[1];
};
template <typename Key, class Comparator>
typename InlineSkipList<Key, Comparator>::Node*
InlineSkipList<Key, Comparator>::NewNode(const Key& key, int height) {
template <class Comparator>
typename InlineSkipList<Comparator>::Node* InlineSkipList<Comparator>::NewNode(
const char* key, int height) {
char* mem = allocator_->AllocateAligned(
sizeof(Node) + sizeof(std::atomic<Node*>) * (height - 1));
return new (mem) Node(key);
}
template <typename Key, class Comparator>
inline InlineSkipList<Key, Comparator>::Iterator::Iterator(
template <class Comparator>
inline InlineSkipList<Comparator>::Iterator::Iterator(
const InlineSkipList* list) {
SetList(list);
}
template <typename Key, class Comparator>
inline void InlineSkipList<Key, Comparator>::Iterator::SetList(
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::SetList(
const InlineSkipList* list) {
list_ = list;
node_ = nullptr;
}
template <typename Key, class Comparator>
inline bool InlineSkipList<Key, Comparator>::Iterator::Valid() const {
template <class Comparator>
inline bool InlineSkipList<Comparator>::Iterator::Valid() const {
return node_ != nullptr;
}
template <typename Key, class Comparator>
inline const Key& InlineSkipList<Key, Comparator>::Iterator::key() const {
template <class Comparator>
inline const char* InlineSkipList<Comparator>::Iterator::key() const {
assert(Valid());
return node_->key;
}
template <typename Key, class Comparator>
inline void InlineSkipList<Key, Comparator>::Iterator::Next() {
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::Next() {
assert(Valid());
node_ = node_->Next(0);
}
template <typename Key, class Comparator>
inline void InlineSkipList<Key, Comparator>::Iterator::Prev() {
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::Prev() {
// Instead of using explicit "prev" links, we just search for the
// last node that falls before key.
assert(Valid());
@ -244,26 +250,26 @@ inline void InlineSkipList<Key, Comparator>::Iterator::Prev() {
}
}
template <typename Key, class Comparator>
inline void InlineSkipList<Key, Comparator>::Iterator::Seek(const Key& target) {
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::Seek(const char* target) {
node_ = list_->FindGreaterOrEqual(target);
}
template <typename Key, class Comparator>
inline void InlineSkipList<Key, Comparator>::Iterator::SeekToFirst() {
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::SeekToFirst() {
node_ = list_->head_->Next(0);
}
template <typename Key, class Comparator>
inline void InlineSkipList<Key, Comparator>::Iterator::SeekToLast() {
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::SeekToLast() {
node_ = list_->FindLast();
if (node_ == list_->head_) {
node_ = nullptr;
}
}
template <typename Key, class Comparator>
int InlineSkipList<Key, Comparator>::RandomHeight() {
template <class Comparator>
int InlineSkipList<Comparator>::RandomHeight() {
auto rnd = Random::GetTLSInstance();
// Increase height with probability 1 in kBranching
@ -276,16 +282,16 @@ int InlineSkipList<Key, Comparator>::RandomHeight() {
return height;
}
template <typename Key, class Comparator>
bool InlineSkipList<Key, Comparator>::KeyIsAfterNode(const Key& key,
Node* n) const {
template <class Comparator>
bool InlineSkipList<Comparator>::KeyIsAfterNode(const char* key,
Node* n) const {
// nullptr n is considered infinite
return (n != nullptr) && (compare_(n->key, key) < 0);
}
template <typename Key, class Comparator>
typename InlineSkipList<Key, Comparator>::Node*
InlineSkipList<Key, Comparator>::FindGreaterOrEqual(const Key& key) const {
template <class Comparator>
typename InlineSkipList<Comparator>::Node*
InlineSkipList<Comparator>::FindGreaterOrEqual(const char* key) const {
// Note: It looks like we could reduce duplication by implementing
// this function as FindLessThan(key)->Next(0), but we wouldn't be able
// to exit early on equality and the result wouldn't even be correct.
@ -315,10 +321,9 @@ InlineSkipList<Key, Comparator>::FindGreaterOrEqual(const Key& key) const {
}
}
template <typename Key, class Comparator>
typename InlineSkipList<Key, Comparator>::Node*
InlineSkipList<Key, Comparator>::FindLessThan(const Key& key,
Node** prev) const {
template <class Comparator>
typename InlineSkipList<Comparator>::Node*
InlineSkipList<Comparator>::FindLessThan(const char* key, Node** prev) const {
Node* x = head_;
int level = GetMaxHeight() - 1;
// KeyIsAfter(key, last_not_after) is definitely false
@ -345,9 +350,9 @@ InlineSkipList<Key, Comparator>::FindLessThan(const Key& key,
}
}
template <typename Key, class Comparator>
typename InlineSkipList<Key, Comparator>::Node*
InlineSkipList<Key, Comparator>::FindLast() const {
template <class Comparator>
typename InlineSkipList<Comparator>::Node*
InlineSkipList<Comparator>::FindLast() const {
Node* x = head_;
int level = GetMaxHeight() - 1;
while (true) {
@ -365,8 +370,8 @@ InlineSkipList<Key, Comparator>::FindLast() const {
}
}
template <typename Key, class Comparator>
uint64_t InlineSkipList<Key, Comparator>::EstimateCount(const Key& key) const {
template <class Comparator>
uint64_t InlineSkipList<Comparator>::EstimateCount(const char* key) const {
uint64_t count = 0;
Node* x = head_;
@ -389,11 +394,11 @@ uint64_t InlineSkipList<Key, Comparator>::EstimateCount(const Key& key) const {
}
}
template <typename Key, class Comparator>
InlineSkipList<Key, Comparator>::InlineSkipList(const Comparator cmp,
Allocator* allocator,
int32_t max_height,
int32_t branching_factor)
template <class Comparator>
InlineSkipList<Comparator>::InlineSkipList(const Comparator cmp,
Allocator* allocator,
int32_t max_height,
int32_t branching_factor)
: kMaxHeight_(max_height),
kBranching_(branching_factor),
kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
@ -417,8 +422,13 @@ InlineSkipList<Key, Comparator>::InlineSkipList(const Comparator cmp,
}
}
template <typename Key, class Comparator>
void InlineSkipList<Key, Comparator>::Insert(const Key& key) {
template <class Comparator>
char* InlineSkipList<Comparator>::AllocateKey(size_t key_size) {
return allocator_->Allocate(key_size);
}
template <class Comparator>
void InlineSkipList<Comparator>::Insert(const char* key) {
// fast path for sequential insertion
if (!KeyIsAfterNode(key, prev_[0]->NoBarrier_Next(0)) &&
(prev_[0] == head_ || KeyIsAfterNode(key, prev_[0]))) {
@ -469,8 +479,8 @@ void InlineSkipList<Key, Comparator>::Insert(const Key& key) {
prev_height_ = height;
}
template <typename Key, class Comparator>
bool InlineSkipList<Key, Comparator>::Contains(const Key& key) const {
template <class Comparator>
bool InlineSkipList<Comparator>::Contains(const char* key) const {
Node* x = FindGreaterOrEqual(key);
if (x != nullptr && Equal(key, x->key)) {
return true;

View File

@ -17,13 +17,24 @@
namespace rocksdb {
// Our test skip list stores 8-byte unsigned integers
typedef uint64_t Key;
static const char* Encode(const uint64_t* key) {
return reinterpret_cast<const char*>(key);
}
static Key Decode(const char* key) {
Key rv;
memcpy(&rv, key, sizeof(Key));
return rv;
}
struct TestComparator {
int operator()(const Key& a, const Key& b) const {
if (a < b) {
int operator()(const char* a, const char* b) const {
if (Decode(a) < Decode(b)) {
return -1;
} else if (a > b) {
} else if (Decode(a) > Decode(b)) {
return +1;
} else {
return 0;
@ -36,14 +47,16 @@ class InlineSkipTest : public testing::Test {};
TEST_F(InlineSkipTest, Empty) {
Arena arena;
TestComparator cmp;
InlineSkipList<Key, TestComparator> list(cmp, &arena);
ASSERT_TRUE(!list.Contains(10));
InlineSkipList<TestComparator> list(cmp, &arena);
Key key = 10;
ASSERT_TRUE(!list.Contains(Encode(&key)));
InlineSkipList<Key, TestComparator>::Iterator iter(&list);
InlineSkipList<TestComparator>::Iterator iter(&list);
ASSERT_TRUE(!iter.Valid());
iter.SeekToFirst();
ASSERT_TRUE(!iter.Valid());
iter.Seek(100);
key = 100;
iter.Seek(Encode(&key));
ASSERT_TRUE(!iter.Valid());
iter.SeekToLast();
ASSERT_TRUE(!iter.Valid());
@ -56,16 +69,18 @@ TEST_F(InlineSkipTest, InsertAndLookup) {
std::set<Key> keys;
Arena arena;
TestComparator cmp;
InlineSkipList<Key, TestComparator> list(cmp, &arena);
InlineSkipList<TestComparator> list(cmp, &arena);
for (int i = 0; i < N; i++) {
Key key = rnd.Next() % R;
if (keys.insert(key).second) {
list.Insert(key);
char* buf = list.AllocateKey(sizeof(Key));
memcpy(buf, &key, sizeof(Key));
list.Insert(buf);
}
}
for (int i = 0; i < R; i++) {
if (list.Contains(i)) {
for (Key i = 0; i < R; i++) {
if (list.Contains(Encode(&i))) {
ASSERT_EQ(keys.count(i), 1U);
} else {
ASSERT_EQ(keys.count(i), 0U);
@ -74,26 +89,27 @@ TEST_F(InlineSkipTest, InsertAndLookup) {
// Simple iterator tests
{
InlineSkipList<Key, TestComparator>::Iterator iter(&list);
InlineSkipList<TestComparator>::Iterator iter(&list);
ASSERT_TRUE(!iter.Valid());
iter.Seek(0);
uint64_t zero = 0;
iter.Seek(Encode(&zero));
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.begin()), iter.key());
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
iter.SeekToFirst();
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.begin()), iter.key());
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
iter.SeekToLast();
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.rbegin()), iter.key());
ASSERT_EQ(*(keys.rbegin()), Decode(iter.key()));
}
// Forward iteration test
for (int i = 0; i < R; i++) {
InlineSkipList<Key, TestComparator>::Iterator iter(&list);
iter.Seek(i);
for (Key i = 0; i < R; i++) {
InlineSkipList<TestComparator>::Iterator iter(&list);
iter.Seek(Encode(&i));
// Compare against model iterator
std::set<Key>::iterator model_iter = keys.lower_bound(i);
@ -103,7 +119,7 @@ TEST_F(InlineSkipTest, InsertAndLookup) {
break;
} else {
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*model_iter, iter.key());
ASSERT_EQ(*model_iter, Decode(iter.key()));
++model_iter;
iter.Next();
}
@ -112,14 +128,14 @@ TEST_F(InlineSkipTest, InsertAndLookup) {
// Backward iteration test
{
InlineSkipList<Key, TestComparator>::Iterator iter(&list);
InlineSkipList<TestComparator>::Iterator iter(&list);
iter.SeekToLast();
// Compare against model iterator
for (std::set<Key>::reverse_iterator model_iter = keys.rbegin();
model_iter != keys.rend(); ++model_iter) {
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*model_iter, iter.key());
ASSERT_EQ(*model_iter, Decode(iter.key()));
iter.Prev();
}
ASSERT_TRUE(!iter.Valid());
@ -210,7 +226,7 @@ class ConcurrentTest {
// InlineSkipList is not protected by mu_. We just use a single writer
// thread to modify it.
InlineSkipList<Key, TestComparator> list_;
InlineSkipList<TestComparator> list_;
public:
ConcurrentTest() : list_(TestComparator(), &arena_) {}
@ -220,7 +236,9 @@ class ConcurrentTest {
const uint32_t k = rnd->Next() % K;
const int g = current_.Get(k) + 1;
const Key new_key = MakeKey(k, g);
list_.Insert(new_key);
char* buf = list_.AllocateKey(sizeof(Key));
memcpy(buf, &new_key, sizeof(Key));
list_.Insert(buf);
current_.Set(k, g);
}
@ -232,14 +250,14 @@ class ConcurrentTest {
}
Key pos = RandomTarget(rnd);
InlineSkipList<Key, TestComparator>::Iterator iter(&list_);
iter.Seek(pos);
InlineSkipList<TestComparator>::Iterator iter(&list_);
iter.Seek(Encode(&pos));
while (true) {
Key current;
if (!iter.Valid()) {
current = MakeKey(K, 0);
} else {
current = iter.key();
current = Decode(iter.key());
ASSERT_TRUE(IsValidKey(current)) << current;
}
ASSERT_LE(pos, current) << "should not go backwards";
@ -276,7 +294,7 @@ class ConcurrentTest {
Key new_target = RandomTarget(rnd);
if (new_target > pos) {
pos = new_target;
iter.Seek(new_target);
iter.Seek(Encode(&new_target));
}
}
}