InlineSkipList to reduce binary search when repeatly seeking to the same position.

Summary: It's not uncommon for memtable reseek to end up with the same position. If it happens three times, find the previous node and use it as a hint for the next time.

Test Plan: Add some unit test coverage.
This commit is contained in:
sdong 2019-05-15 18:55:30 -07:00
parent 29a198564d
commit 5447d7c83f
2 changed files with 90 additions and 4 deletions

View File

@ -176,6 +176,13 @@ class InlineSkipList {
private:
const InlineSkipList* list_;
Node* node_;
// A hint for reseeking to the next position.
// This points to the previous position of guessed seek result
Node* hint_prev_ = nullptr;
// we can potentially remove this memory usage by hacking into
// `hint_prev_` but the code will be harder to read. Can consider it
// if it is needed.
int num_seek_to_same_pos = 0;
// Intentionally copyable
};
@ -389,7 +396,49 @@ inline void InlineSkipList<Comparator>::Iterator::Prev() {
template <class Comparator>
inline void InlineSkipList<Comparator>::Iterator::Seek(const char* target) {
// Generate hint when needed.
// This will make current Seek() slightly slower, but will benefit
// following seeks.
const int kThresholdSetupHint = 2;
if (num_seek_to_same_pos == kThresholdSetupHint && node_ != nullptr) {
// Most likely reseeked to the same position, though there are other
// cases, but he counter doesn't have to be totally accurate.
// Hint will be rechecked again, so a wrong hint will never generate
// wrong results.
// Only generate hint when we just hit kThresholdSetupHint.
hint_prev_ = list_->FindLessThan(node_->Key());
// We don't handle the case that we seek to the first or beyond the last
// entry. We couldn't handle it but not doing it for now to keep the
// code simpler.
if (hint_prev_ == list_->head_) {
hint_prev_ = nullptr;
}
}
if (hint_prev_ != nullptr) {
// try reseek to the same position.
Node* hint_node = hint_prev_->Next(0);
assert(hint_node != nullptr);
if (list_->LessThan(hint_prev_->Key(), target) &&
!list_->LessThan(hint_node->Key(), target)) {
node_ = hint_node;
num_seek_to_same_pos++;
return;
} else {
// Hint didn't work. Reset it.
hint_prev_ = nullptr;
}
}
Node* prev_node_ = node_; // For tracking seeking to the same position.
node_ = list_->FindGreaterOrEqual(target);
if (prev_node_ == node_) {
num_seek_to_same_pos++;
} else {
num_seek_to_same_pos = 0;
}
}
template <class Comparator>

View File

@ -153,16 +153,53 @@ TEST_F(InlineSkipTest, InsertAndLookup) {
InlineSkipList<TestComparator>::Iterator iter(&list);
ASSERT_TRUE(!iter.Valid());
uint64_t zero = 0;
iter.Seek(Encode(&zero));
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
// Repeat five times, to cover reseek code.
// In this case, reseek shouldn't be triggered as
// There is no prev of the seek results.
for (int i = 0; i < 5; i++) {
uint64_t zero = 0;
iter.Seek(Encode(&zero));
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
}
uint64_t max_key = R - 1;
iter.SeekForPrev(Encode(&max_key));
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.rbegin()), Decode(iter.key()));
// Repeat seek five times, to cover reseek code.
// In this case, reseek should be triggered.
for (int i = 0; i < 5; i++) {
Key last_key = *keys.rbegin();
iter.Seek(Encode(&last_key));
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.rbegin()), Decode(iter.key()));
}
// Repeat seek to the first key five times, to cover reseek code.
// Doing some Next() in the middle.
// In this case, reseek should be triggered.
for (int i = 0; i < 5; i++) {
uint64_t zero = 0;
iter.Seek(Encode(&zero));
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));
if (i % 2 == 0) {
iter.Next();
} else {
iter.Prev();
}
}
// Repeat seek five times for not found key, to cover reseek code.
// In this case, reseek should be not triggered.
uint64_t large_key = R + 1;
for (int i = 0; i < 5; i++) {
iter.Seek(Encode(&large_key));
ASSERT_FALSE(iter.Valid());
}
iter.SeekToFirst();
ASSERT_TRUE(iter.Valid());
ASSERT_EQ(*(keys.begin()), Decode(iter.key()));