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:
parent
29a198564d
commit
5447d7c83f
@ -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>
|
||||
|
@ -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()));
|
||||
|
Loading…
Reference in New Issue
Block a user