mirror of
https://github.com/facebook/rocksdb.git
synced 2024-12-03 14:52:53 +00:00
dd23e84cad
Summary: GetApproximateMemTableStats() could return some bad results with the standard skip list memtable. See this new db_bench test showing the dismal distribution of results when the actual number of entries in range is 1000: ``` $ ./db_bench --benchmarks=filluniquerandom,approximatememtablestats,readrandom --value_size=1 --num=1000000 --batch_size=1000 ... filluniquerandom : 1.391 micros/op 718915 ops/sec 1.391 seconds 1000000 operations; 11.7 MB/s approximatememtablestats : 3.711 micros/op 269492 ops/sec 3.711 seconds 1000000 operations; Reported entry count stats (expected 1000): Count: 1000000 Average: 2344.1611 StdDev: 26587.27 Min: 0 Median: 965.8555 Max: 835273 Percentiles: P50: 965.86 P75: 1610.77 P99: 12618.01 P99.9: 74991.58 P99.99: 830970.97 ------------------------------------------------------ [ 0, 1 ] 131344 13.134% 13.134% ### ( 1, 2 ] 115 0.011% 13.146% ( 2, 3 ] 106 0.011% 13.157% ( 3, 4 ] 190 0.019% 13.176% ( 4, 6 ] 214 0.021% 13.197% ( 6, 10 ] 522 0.052% 13.249% ( 10, 15 ] 748 0.075% 13.324% ( 15, 22 ] 1002 0.100% 13.424% ( 22, 34 ] 1948 0.195% 13.619% ( 34, 51 ] 3067 0.307% 13.926% ( 51, 76 ] 4213 0.421% 14.347% ( 76, 110 ] 5721 0.572% 14.919% ( 110, 170 ] 11375 1.137% 16.056% ( 170, 250 ] 17928 1.793% 17.849% ( 250, 380 ] 36597 3.660% 21.509% # ( 380, 580 ] 77882 7.788% 29.297% ## ( 580, 870 ] 160193 16.019% 45.317% ### ( 870, 1300 ] 210098 21.010% 66.326% #### ( 1300, 1900 ] 167461 16.746% 83.072% ### ( 1900, 2900 ] 78678 7.868% 90.940% ## ( 2900, 4400 ] 47743 4.774% 95.715% # ( 4400, 6600 ] 17650 1.765% 97.480% ( 6600, 9900 ] 11895 1.190% 98.669% ( 9900, 14000 ] 4993 0.499% 99.168% ( 14000, 22000 ] 2384 0.238% 99.407% ( 22000, 33000 ] 1966 0.197% 99.603% ( 50000, 75000 ] 2968 0.297% 99.900% ( 570000, 860000 ] 999 0.100% 100.000% readrandom : 1.967 micros/op 508487 ops/sec 1.967 seconds 1000000 operations; 8.2 MB/s (1000000 of 1000000 found) ``` Perhaps the only good thing to say about the old implementation was that it was fast, though apparently not that fast. I've implemented a much more robust and reasonably fast new version of the function. It's still logarithmic but with some larger constant factors. The standard deviation from true count is around 20% or less, and roughly the CPU cost of two memtable point look-ups. See code comments for detail. ``` $ ./db_bench --benchmarks=filluniquerandom,approximatememtablestats,readrandom --value_size=1 --num=1000000 --batch_size=1000 ... filluniquerandom : 1.478 micros/op 676434 ops/sec 1.478 seconds 1000000 operations; 11.0 MB/s approximatememtablestats : 2.694 micros/op 371157 ops/sec 2.694 seconds 1000000 operations; Reported entry count stats (expected 1000): Count: 1000000 Average: 1073.5158 StdDev: 197.80 Min: 608 Median: 1079.9506 Max: 2176 Percentiles: P50: 1079.95 P75: 1223.69 P99: 1852.36 P99.9: 1898.70 P99.99: 2176.00 ------------------------------------------------------ ( 580, 870 ] 134848 13.485% 13.485% ### ( 870, 1300 ] 747868 74.787% 88.272% ############### ( 1300, 1900 ] 116536 11.654% 99.925% ## ( 1900, 2900 ] 748 0.075% 100.000% readrandom : 1.997 micros/op 500654 ops/sec 1.997 seconds 1000000 operations; 8.1 MB/s (1000000 of 1000000 found) ``` We can already see that the distribution of results is dramatically better and wonderfully normal-looking, with relative standard deviation around 20%. The function is also FASTER, at least with these parameters. Let's look how this behavior generalizes, first *much* larger range: ``` $ ./db_bench --benchmarks=filluniquerandom,approximatememtablestats,readrandom --value_size=1 --num=1000000 --batch_size=30000 filluniquerandom : 1.390 micros/op 719654 ops/sec 1.376 seconds 990000 operations; 11.7 MB/s approximatememtablestats : 1.129 micros/op 885649 ops/sec 1.129 seconds 1000000 operations; Reported entry count stats (expected 30000): Count: 1000000 Average: 31098.8795 StdDev: 3601.47 Min: 21504 Median: 29333.9303 Max: 43008 Percentiles: P50: 29333.93 P75: 33018.00 P99: 43008.00 P99.9: 43008.00 P99.99: 43008.00 ------------------------------------------------------ ( 14000, 22000 ] 408 0.041% 0.041% ( 22000, 33000 ] 749327 74.933% 74.974% ############### ( 33000, 50000 ] 250265 25.027% 100.000% ##### readrandom : 1.894 micros/op 528083 ops/sec 1.894 seconds 1000000 operations; 8.5 MB/s (989989 of 1000000 found) ``` This is *even faster* and relatively *more accurate*, with relative standard deviation closer to 10%. Code comments explain why. Now let's look at smaller ranges. Implementation quirks or conveniences: * When actual number in range is >= 40, the minimum return value is 40. * When the actual is <= 10, it is guaranteed to return that actual number. ``` $ ./db_bench --benchmarks=filluniquerandom,approximatememtablestats,readrandom --value_size=1 --num=1000000 --batch_size=75 ... filluniquerandom : 1.417 micros/op 705668 ops/sec 1.417 seconds 999975 operations; 11.4 MB/s approximatememtablestats : 3.342 micros/op 299197 ops/sec 3.342 seconds 1000000 operations; Reported entry count stats (expected 75): Count: 1000000 Average: 75.1210 StdDev: 15.02 Min: 40 Median: 71.9395 Max: 256 Percentiles: P50: 71.94 P75: 89.69 P99: 119.12 P99.9: 166.68 P99.99: 229.78 ------------------------------------------------------ ( 34, 51 ] 38867 3.887% 3.887% # ( 51, 76 ] 550554 55.055% 58.942% ########### ( 76, 110 ] 398854 39.885% 98.828% ######## ( 110, 170 ] 11353 1.135% 99.963% ( 170, 250 ] 364 0.036% 99.999% ( 250, 380 ] 8 0.001% 100.000% readrandom : 1.861 micros/op 537224 ops/sec 1.861 seconds 1000000 operations; 8.7 MB/s (999974 of 1000000 found) $ ./db_bench --benchmarks=filluniquerandom,approximatememtablestats,readrandom --value_size=1 --num=1000000 --batch_size=25 ... filluniquerandom : 1.501 micros/op 666283 ops/sec 1.501 seconds 1000000 operations; 10.8 MB/s approximatememtablestats : 5.118 micros/op 195401 ops/sec 5.118 seconds 1000000 operations; Reported entry count stats (expected 25): Count: 1000000 Average: 26.2392 StdDev: 4.58 Min: 25 Median: 28.4590 Max: 72 Percentiles: P50: 28.46 P75: 31.69 P99: 49.27 P99.9: 67.95 P99.99: 72.00 ------------------------------------------------------ ( 22, 34 ] 928936 92.894% 92.894% ################### ( 34, 51 ] 67960 6.796% 99.690% # ( 51, 76 ] 3104 0.310% 100.000% readrandom : 1.892 micros/op 528595 ops/sec 1.892 seconds 1000000 operations; 8.6 MB/s (1000000 of 1000000 found) $ ./db_bench --benchmarks=filluniquerandom,approximatememtablestats,readrandom --value_size=1 --num=1000000 --batch_size=10 ... filluniquerandom : 1.642 micros/op 608916 ops/sec 1.642 seconds 1000000 operations; 9.9 MB/s approximatememtablestats : 3.042 micros/op 328721 ops/sec 3.042 seconds 1000000 operations; Reported entry count stats (expected 10): Count: 1000000 Average: 10.0000 StdDev: 0.00 Min: 10 Median: 10.0000 Max: 10 Percentiles: P50: 10.00 P75: 10.00 P99: 10.00 P99.9: 10.00 P99.99: 10.00 ------------------------------------------------------ ( 6, 10 ] 1000000 100.000% 100.000% #################### readrandom : 1.805 micros/op 554126 ops/sec 1.805 seconds 1000000 operations; 9.0 MB/s (1000000 of 1000000 found) ``` Remarkably consistent. Pull Request resolved: https://github.com/facebook/rocksdb/pull/13047 Test Plan: new db_bench test for both performance and accuracy (see above); added to crash test; unit test updated. Reviewed By: cbi42 Differential Revision: D63722003 Pulled By: pdillinger fbshipit-source-id: cfc8613c085e87c17ecec22d82601aac2a5a1b26
522 lines
16 KiB
C++
522 lines
16 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
//
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
//
|
|
// Thread safety
|
|
// -------------
|
|
//
|
|
// Writes require external synchronization, most likely a mutex.
|
|
// Reads require a guarantee that the SkipList will not be destroyed
|
|
// while the read is in progress. Apart from that, reads progress
|
|
// without any internal locking or synchronization.
|
|
//
|
|
// Invariants:
|
|
//
|
|
// (1) Allocated nodes are never deleted until the SkipList is
|
|
// destroyed. This is trivially guaranteed by the code since we
|
|
// never delete any skip list nodes.
|
|
//
|
|
// (2) The contents of a Node except for the next/prev pointers are
|
|
// immutable after the Node has been linked into the SkipList.
|
|
// Only Insert() modifies the list, and it is careful to initialize
|
|
// a node and use release-stores to publish the nodes in one or
|
|
// more lists.
|
|
//
|
|
// ... prev vs. next pointer ordering ...
|
|
//
|
|
|
|
#pragma once
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <atomic>
|
|
|
|
#include "memory/allocator.h"
|
|
#include "port/port.h"
|
|
#include "util/random.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
template <typename Key, class Comparator>
|
|
class SkipList {
|
|
private:
|
|
struct Node;
|
|
|
|
public:
|
|
// Create a new SkipList object that will use "cmp" for comparing keys,
|
|
// and will allocate memory using "*allocator". Objects allocated in the
|
|
// allocator must remain allocated for the lifetime of the skiplist object.
|
|
explicit SkipList(Comparator cmp, Allocator* allocator,
|
|
int32_t max_height = 12, int32_t branching_factor = 4);
|
|
// No copying allowed
|
|
SkipList(const SkipList&) = delete;
|
|
void operator=(const SkipList&) = delete;
|
|
|
|
// Insert key into the list.
|
|
// REQUIRES: nothing that compares equal to key is currently in the list.
|
|
void Insert(const Key& key);
|
|
|
|
// Returns true iff an entry that compares equal to key is in the list.
|
|
bool Contains(const Key& key) const;
|
|
|
|
// Return estimated number of entries from `start_ikey` to `end_ikey`.
|
|
uint64_t ApproximateNumEntries(const Slice& start_ikey,
|
|
const Slice& end_ikey) const;
|
|
|
|
// Iteration over the contents of a skip list
|
|
class Iterator {
|
|
public:
|
|
// Initialize an iterator over the specified list.
|
|
// The returned iterator is not valid.
|
|
explicit Iterator(const SkipList* list);
|
|
|
|
// Change the underlying skiplist used for this iterator
|
|
// This enables us not changing the iterator without deallocating
|
|
// an old one and then allocating a new one
|
|
void SetList(const SkipList* list);
|
|
|
|
// Returns true iff the iterator is positioned at a valid node.
|
|
bool Valid() const;
|
|
|
|
// Returns the key at the current position.
|
|
// REQUIRES: Valid()
|
|
const Key& key() const;
|
|
|
|
// Advances to the next position.
|
|
// REQUIRES: Valid()
|
|
void Next();
|
|
|
|
// Advances to the previous position.
|
|
// REQUIRES: Valid()
|
|
void Prev();
|
|
|
|
// Advance to the first entry with a key >= target
|
|
void Seek(const Key& target);
|
|
|
|
// Retreat to the last entry with a key <= target
|
|
void SeekForPrev(const Key& target);
|
|
|
|
// Position at the first entry in list.
|
|
// Final state of iterator is Valid() iff list is not empty.
|
|
void SeekToFirst();
|
|
|
|
// Position at the last entry in list.
|
|
// Final state of iterator is Valid() iff list is not empty.
|
|
void SeekToLast();
|
|
|
|
private:
|
|
const SkipList* list_;
|
|
Node* node_;
|
|
// Intentionally copyable
|
|
};
|
|
|
|
private:
|
|
const uint16_t kMaxHeight_;
|
|
const uint16_t kBranching_;
|
|
const uint32_t kScaledInverseBranching_;
|
|
|
|
// Immutable after construction
|
|
Comparator const compare_;
|
|
Allocator* const allocator_; // Allocator used for allocations of nodes
|
|
|
|
Node* const head_;
|
|
|
|
// Modified only by Insert(). Read racily by readers, but stale
|
|
// values are ok.
|
|
std::atomic<int> max_height_; // Height of the entire list
|
|
|
|
// Used for optimizing sequential insert patterns. Tricky. prev_[i] for
|
|
// i up to max_height_ is the predecessor of prev_[0] and prev_height_
|
|
// is the height of prev_[0]. prev_[0] can only be equal to head before
|
|
// insertion, in which case max_height_ and prev_height_ are 1.
|
|
Node** prev_;
|
|
int32_t prev_height_;
|
|
|
|
inline int GetMaxHeight() const {
|
|
return max_height_.load(std::memory_order_relaxed);
|
|
}
|
|
|
|
Node* NewNode(const Key& key, int height);
|
|
int RandomHeight();
|
|
bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); }
|
|
bool LessThan(const Key& a, const Key& 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;
|
|
|
|
// Returns the earliest node with a key >= key.
|
|
// Return nullptr if there is no such node.
|
|
Node* FindGreaterOrEqual(const Key& 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;
|
|
|
|
// Return the last node in the list.
|
|
// Return head_ if list is empty.
|
|
Node* FindLast() const;
|
|
};
|
|
|
|
// Implementation details follow
|
|
template <typename Key, class Comparator>
|
|
struct SkipList<Key, Comparator>::Node {
|
|
explicit Node(const Key& k) : key(k) {}
|
|
|
|
Key const key;
|
|
|
|
// Accessors/mutators for links. Wrapped in methods so we can
|
|
// add the appropriate barriers as necessary.
|
|
Node* Next(int n) {
|
|
assert(n >= 0);
|
|
// Use an 'acquire load' so that we observe a fully initialized
|
|
// version of the returned Node.
|
|
return (next_[n].load(std::memory_order_acquire));
|
|
}
|
|
void SetNext(int n, Node* x) {
|
|
assert(n >= 0);
|
|
// Use a 'release store' so that anybody who reads through this
|
|
// pointer observes a fully initialized version of the inserted node.
|
|
next_[n].store(x, std::memory_order_release);
|
|
}
|
|
|
|
// No-barrier variants that can be safely used in a few locations.
|
|
Node* NoBarrier_Next(int n) {
|
|
assert(n >= 0);
|
|
return next_[n].load(std::memory_order_relaxed);
|
|
}
|
|
void NoBarrier_SetNext(int n, Node* x) {
|
|
assert(n >= 0);
|
|
next_[n].store(x, std::memory_order_relaxed);
|
|
}
|
|
|
|
private:
|
|
// Array of length equal to the node height. next_[0] is lowest level link.
|
|
std::atomic<Node*> next_[1];
|
|
};
|
|
|
|
template <typename Key, class Comparator>
|
|
typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::NewNode(
|
|
const Key& 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 SkipList<Key, Comparator>::Iterator::Iterator(const SkipList* list) {
|
|
SetList(list);
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::SetList(const SkipList* list) {
|
|
list_ = list;
|
|
node_ = nullptr;
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline bool SkipList<Key, Comparator>::Iterator::Valid() const {
|
|
return node_ != nullptr;
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline const Key& SkipList<Key, Comparator>::Iterator::key() const {
|
|
assert(Valid());
|
|
return node_->key;
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::Next() {
|
|
assert(Valid());
|
|
node_ = node_->Next(0);
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::Prev() {
|
|
// Instead of using explicit "prev" links, we just search for the
|
|
// last node that falls before key.
|
|
assert(Valid());
|
|
node_ = list_->FindLessThan(node_->key);
|
|
if (node_ == list_->head_) {
|
|
node_ = nullptr;
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::Seek(const Key& target) {
|
|
node_ = list_->FindGreaterOrEqual(target);
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::SeekForPrev(
|
|
const Key& target) {
|
|
Seek(target);
|
|
if (!Valid()) {
|
|
SeekToLast();
|
|
}
|
|
while (Valid() && list_->LessThan(target, key())) {
|
|
Prev();
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::SeekToFirst() {
|
|
node_ = list_->head_->Next(0);
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
inline void SkipList<Key, Comparator>::Iterator::SeekToLast() {
|
|
node_ = list_->FindLast();
|
|
if (node_ == list_->head_) {
|
|
node_ = nullptr;
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
int SkipList<Key, Comparator>::RandomHeight() {
|
|
auto rnd = Random::GetTLSInstance();
|
|
|
|
// Increase height with probability 1 in kBranching
|
|
int height = 1;
|
|
while (height < kMaxHeight_ && rnd->Next() < kScaledInverseBranching_) {
|
|
height++;
|
|
}
|
|
assert(height > 0);
|
|
assert(height <= kMaxHeight_);
|
|
return height;
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
bool SkipList<Key, Comparator>::KeyIsAfterNode(const Key& key, Node* n) const {
|
|
// nullptr n is considered infinite
|
|
return (n != nullptr) && (compare_(n->key, key) < 0);
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
typename SkipList<Key, Comparator>::Node*
|
|
SkipList<Key, Comparator>::FindGreaterOrEqual(const Key& 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.
|
|
// A concurrent insert might occur after FindLessThan(key) but before
|
|
// we get a chance to call Next(0).
|
|
Node* x = head_;
|
|
int level = GetMaxHeight() - 1;
|
|
Node* last_bigger = nullptr;
|
|
while (true) {
|
|
assert(x != nullptr);
|
|
Node* next = x->Next(level);
|
|
// Make sure the lists are sorted
|
|
assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x));
|
|
// Make sure we haven't overshot during our search
|
|
assert(x == head_ || KeyIsAfterNode(key, x));
|
|
int cmp =
|
|
(next == nullptr || next == last_bigger) ? 1 : compare_(next->key, key);
|
|
if (cmp == 0 || (cmp > 0 && level == 0)) {
|
|
return next;
|
|
} else if (cmp < 0) {
|
|
// Keep searching in this list
|
|
x = next;
|
|
} else {
|
|
// Switch to next list, reuse compare_() result
|
|
last_bigger = next;
|
|
level--;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
typename SkipList<Key, Comparator>::Node*
|
|
SkipList<Key, Comparator>::FindLessThan(const Key& key, Node** prev) const {
|
|
Node* x = head_;
|
|
int level = GetMaxHeight() - 1;
|
|
// KeyIsAfter(key, last_not_after) is definitely false
|
|
Node* last_not_after = nullptr;
|
|
while (true) {
|
|
assert(x != nullptr);
|
|
Node* next = x->Next(level);
|
|
assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x));
|
|
assert(x == head_ || KeyIsAfterNode(key, x));
|
|
if (next != last_not_after && KeyIsAfterNode(key, next)) {
|
|
// Keep searching in this list
|
|
x = next;
|
|
} else {
|
|
if (prev != nullptr) {
|
|
prev[level] = x;
|
|
}
|
|
if (level == 0) {
|
|
return x;
|
|
} else {
|
|
// Switch to next list, reuse KeyIUsAfterNode() result
|
|
last_not_after = next;
|
|
level--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::FindLast()
|
|
const {
|
|
Node* x = head_;
|
|
int level = GetMaxHeight() - 1;
|
|
while (true) {
|
|
Node* next = x->Next(level);
|
|
if (next == nullptr) {
|
|
if (level == 0) {
|
|
return x;
|
|
} else {
|
|
// Switch to next list
|
|
level--;
|
|
}
|
|
} else {
|
|
x = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
uint64_t SkipList<Key, Comparator>::ApproximateNumEntries(
|
|
const Slice& start_ikey, const Slice& end_ikey) const {
|
|
// See InlineSkipList<Comparator>::ApproximateNumEntries() (copy-paste)
|
|
Node* lb = head_;
|
|
Node* ub = nullptr;
|
|
uint64_t count = 0;
|
|
for (int level = GetMaxHeight() - 1; level >= 0; level--) {
|
|
auto sufficient_samples = static_cast<uint64_t>(level) * kBranching_ + 10U;
|
|
if (count >= sufficient_samples) {
|
|
// No more counting; apply powers of kBranching and avoid floating point
|
|
count *= kBranching_;
|
|
continue;
|
|
}
|
|
count = 0;
|
|
Node* next;
|
|
// Get a more precise lower bound (for start key)
|
|
for (;;) {
|
|
next = lb->Next(level);
|
|
if (next == ub) {
|
|
break;
|
|
}
|
|
assert(next != nullptr);
|
|
if (compare_(next->Key(), start_ikey) >= 0) {
|
|
break;
|
|
}
|
|
lb = next;
|
|
}
|
|
// Count entries on this level until upper bound (for end key)
|
|
for (;;) {
|
|
if (next == ub) {
|
|
break;
|
|
}
|
|
assert(next != nullptr);
|
|
if (compare_(next->Key(), end_ikey) >= 0) {
|
|
// Save refined upper bound to potentially save key comparison
|
|
ub = next;
|
|
break;
|
|
}
|
|
count++;
|
|
next = next->Next(level);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator,
|
|
int32_t max_height,
|
|
int32_t branching_factor)
|
|
: kMaxHeight_(static_cast<uint16_t>(max_height)),
|
|
kBranching_(static_cast<uint16_t>(branching_factor)),
|
|
kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_),
|
|
compare_(cmp),
|
|
allocator_(allocator),
|
|
head_(NewNode(0 /* any key will do */, max_height)),
|
|
max_height_(1),
|
|
prev_height_(1) {
|
|
assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height));
|
|
assert(branching_factor > 0 &&
|
|
kBranching_ == static_cast<uint32_t>(branching_factor));
|
|
assert(kScaledInverseBranching_ > 0);
|
|
// Allocate the prev_ Node* array, directly from the passed-in allocator.
|
|
// prev_ does not need to be freed, as its life cycle is tied up with
|
|
// the allocator as a whole.
|
|
prev_ = reinterpret_cast<Node**>(
|
|
allocator_->AllocateAligned(sizeof(Node*) * kMaxHeight_));
|
|
for (int i = 0; i < kMaxHeight_; i++) {
|
|
head_->SetNext(i, nullptr);
|
|
prev_[i] = head_;
|
|
}
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
void SkipList<Key, Comparator>::Insert(const Key& key) {
|
|
// fast path for sequential insertion
|
|
if (!KeyIsAfterNode(key, prev_[0]->NoBarrier_Next(0)) &&
|
|
(prev_[0] == head_ || KeyIsAfterNode(key, prev_[0]))) {
|
|
assert(prev_[0] != head_ || (prev_height_ == 1 && GetMaxHeight() == 1));
|
|
|
|
// Outside of this method prev_[1..max_height_] is the predecessor
|
|
// of prev_[0], and prev_height_ refers to prev_[0]. Inside Insert
|
|
// prev_[0..max_height - 1] is the predecessor of key. Switch from
|
|
// the external state to the internal
|
|
for (int i = 1; i < prev_height_; i++) {
|
|
prev_[i] = prev_[0];
|
|
}
|
|
} else {
|
|
// TODO(opt): we could use a NoBarrier predecessor search as an
|
|
// optimization for architectures where memory_order_acquire needs
|
|
// a synchronization instruction. Doesn't matter on x86
|
|
FindLessThan(key, prev_);
|
|
}
|
|
|
|
// Our data structure does not allow duplicate insertion
|
|
assert(prev_[0]->Next(0) == nullptr || !Equal(key, prev_[0]->Next(0)->key));
|
|
|
|
int height = RandomHeight();
|
|
if (height > GetMaxHeight()) {
|
|
for (int i = GetMaxHeight(); i < height; i++) {
|
|
prev_[i] = head_;
|
|
}
|
|
// fprintf(stderr, "Change height from %d to %d\n", max_height_, height);
|
|
|
|
// It is ok to mutate max_height_ without any synchronization
|
|
// with concurrent readers. A concurrent reader that observes
|
|
// the new value of max_height_ will see either the old value of
|
|
// new level pointers from head_ (nullptr), or a new value set in
|
|
// the loop below. In the former case the reader will
|
|
// immediately drop to the next level since nullptr sorts after all
|
|
// keys. In the latter case the reader will use the new node.
|
|
max_height_.store(height, std::memory_order_relaxed);
|
|
}
|
|
|
|
Node* x = NewNode(key, height);
|
|
for (int i = 0; i < height; i++) {
|
|
// NoBarrier_SetNext() suffices since we will add a barrier when
|
|
// we publish a pointer to "x" in prev[i].
|
|
x->NoBarrier_SetNext(i, prev_[i]->NoBarrier_Next(i));
|
|
prev_[i]->SetNext(i, x);
|
|
}
|
|
prev_[0] = x;
|
|
prev_height_ = height;
|
|
}
|
|
|
|
template <typename Key, class Comparator>
|
|
bool SkipList<Key, Comparator>::Contains(const Key& key) const {
|
|
Node* x = FindGreaterOrEqual(key);
|
|
if (x != nullptr && Equal(key, x->key)) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|