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
main
Nathan Bronson 9 years ago
parent 78812ec6bf
commit 5201729545
  1. 126
      db/inlineskiplist.h
  2. 74
      db/inlineskiplist_test.cc

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

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

Loading…
Cancel
Save