diff --git a/memtable/inlineskiplist.h b/memtable/inlineskiplist.h index 1ef8f2b6d..8c531f528 100644 --- a/memtable/inlineskiplist.h +++ b/memtable/inlineskiplist.h @@ -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::Iterator::Prev() { template inline void InlineSkipList::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 diff --git a/memtable/inlineskiplist_test.cc b/memtable/inlineskiplist_test.cc index b416ef7c5..cb1a537d6 100644 --- a/memtable/inlineskiplist_test.cc +++ b/memtable/inlineskiplist_test.cc @@ -153,16 +153,53 @@ TEST_F(InlineSkipTest, InsertAndLookup) { InlineSkipList::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()));