/src/rocksdb/memtable/skiplist.h
Line | Count | Source |
1 | | // Copyright (c) 2011-present, Facebook, Inc. All rights reserved. |
2 | | // This source code is licensed under both the GPLv2 (found in the |
3 | | // COPYING file in the root directory) and Apache 2.0 License |
4 | | // (found in the LICENSE.Apache file in the root directory). |
5 | | // |
6 | | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. |
7 | | // Use of this source code is governed by a BSD-style license that can be |
8 | | // found in the LICENSE file. See the AUTHORS file for names of contributors. |
9 | | // |
10 | | // Thread safety |
11 | | // ------------- |
12 | | // |
13 | | // Writes require external synchronization, most likely a mutex. |
14 | | // Reads require a guarantee that the SkipList will not be destroyed |
15 | | // while the read is in progress. Apart from that, reads progress |
16 | | // without any internal locking or synchronization. |
17 | | // |
18 | | // Invariants: |
19 | | // |
20 | | // (1) Allocated nodes are never deleted until the SkipList is |
21 | | // destroyed. This is trivially guaranteed by the code since we |
22 | | // never delete any skip list nodes. |
23 | | // |
24 | | // (2) The contents of a Node except for the next/prev pointers are |
25 | | // immutable after the Node has been linked into the SkipList. |
26 | | // Only Insert() modifies the list, and it is careful to initialize |
27 | | // a node and use release-stores to publish the nodes in one or |
28 | | // more lists. |
29 | | // |
30 | | // ... prev vs. next pointer ordering ... |
31 | | // |
32 | | |
33 | | #pragma once |
34 | | #include <assert.h> |
35 | | #include <stdlib.h> |
36 | | |
37 | | #include "memory/allocator.h" |
38 | | #include "port/port.h" |
39 | | #include "util/atomic.h" |
40 | | #include "util/random.h" |
41 | | |
42 | | namespace ROCKSDB_NAMESPACE { |
43 | | |
44 | | template <typename Key, class Comparator> |
45 | | class SkipList { |
46 | | private: |
47 | | struct Node; |
48 | | |
49 | | public: |
50 | | // Create a new SkipList object that will use "cmp" for comparing keys, |
51 | | // and will allocate memory using "*allocator". Objects allocated in the |
52 | | // allocator must remain allocated for the lifetime of the skiplist object. |
53 | | explicit SkipList(Comparator cmp, Allocator* allocator, |
54 | | int32_t max_height = 12, int32_t branching_factor = 4); |
55 | | // No copying allowed |
56 | | SkipList(const SkipList&) = delete; |
57 | | void operator=(const SkipList&) = delete; |
58 | | |
59 | | // Insert key into the list. |
60 | | // REQUIRES: nothing that compares equal to key is currently in the list. |
61 | | void Insert(const Key& key); |
62 | | |
63 | | // Returns true iff an entry that compares equal to key is in the list. |
64 | | bool Contains(const Key& key) const; |
65 | | |
66 | | // Return estimated number of entries from `start_ikey` to `end_ikey`. |
67 | | uint64_t ApproximateNumEntries(const Slice& start_ikey, |
68 | | const Slice& end_ikey) const; |
69 | | |
70 | | // Iteration over the contents of a skip list |
71 | | class Iterator { |
72 | | public: |
73 | | // Initialize an iterator over the specified list. |
74 | | // The returned iterator is not valid. |
75 | | explicit Iterator(const SkipList* list); |
76 | | |
77 | | // Change the underlying skiplist used for this iterator |
78 | | // This enables us not changing the iterator without deallocating |
79 | | // an old one and then allocating a new one |
80 | | void SetList(const SkipList* list); |
81 | | |
82 | | // Returns true iff the iterator is positioned at a valid node. |
83 | | bool Valid() const; |
84 | | |
85 | | // Returns the key at the current position. |
86 | | // REQUIRES: Valid() |
87 | | const Key& key() const; |
88 | | |
89 | | // Advances to the next position. |
90 | | // REQUIRES: Valid() |
91 | | void Next(); |
92 | | |
93 | | // Advances to the previous position. |
94 | | // REQUIRES: Valid() |
95 | | void Prev(); |
96 | | |
97 | | // Advance to the first entry with a key >= target |
98 | | void Seek(const Key& target); |
99 | | |
100 | | // Retreat to the last entry with a key <= target |
101 | | void SeekForPrev(const Key& target); |
102 | | |
103 | | // Position at the first entry in list. |
104 | | // Final state of iterator is Valid() iff list is not empty. |
105 | | void SeekToFirst(); |
106 | | |
107 | | // Position at the last entry in list. |
108 | | // Final state of iterator is Valid() iff list is not empty. |
109 | | void SeekToLast(); |
110 | | |
111 | | private: |
112 | | const SkipList* list_; |
113 | | Node* node_; |
114 | | // Intentionally copyable |
115 | | }; |
116 | | |
117 | | private: |
118 | | const uint16_t kMaxHeight_; |
119 | | const uint16_t kBranching_; |
120 | | const uint32_t kScaledInverseBranching_; |
121 | | |
122 | | // Immutable after construction |
123 | | Comparator const compare_; |
124 | | Allocator* const allocator_; // Allocator used for allocations of nodes |
125 | | |
126 | | Node* const head_; |
127 | | |
128 | | // Modified only by Insert(). Read racily by readers, but stale |
129 | | // values are ok. |
130 | | RelaxedAtomic<int> max_height_; // Height of the entire list |
131 | | |
132 | | // Used for optimizing sequential insert patterns. Tricky. prev_[i] for |
133 | | // i up to max_height_ is the predecessor of prev_[0] and prev_height_ |
134 | | // is the height of prev_[0]. prev_[0] can only be equal to head before |
135 | | // insertion, in which case max_height_ and prev_height_ are 1. |
136 | | int32_t prev_height_; |
137 | | Node** prev_; |
138 | | |
139 | 0 | inline int GetMaxHeight() const { return max_height_.LoadRelaxed(); }Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::GetMaxHeight() const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::GetMaxHeight() const |
140 | | |
141 | | Node* NewNode(const Key& key, int height); |
142 | | int RandomHeight(); |
143 | 0 | bool Equal(const Key& a, const Key& b) const { return (compare_(a, b) == 0); } |
144 | 0 | bool LessThan(const Key& a, const Key& b) const { |
145 | 0 | return (compare_(a, b) < 0); |
146 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::LessThan(rocksdb::WriteBatchIndexEntry* const&, rocksdb::WriteBatchIndexEntry* const&) const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::LessThan(char const* const&, char const* const&) const |
147 | | |
148 | | // Return true if key is greater than the data stored in "n" |
149 | | bool KeyIsAfterNode(const Key& key, Node* n) const; |
150 | | |
151 | | // Returns the earliest node with a key >= key. |
152 | | // Return nullptr if there is no such node. |
153 | | Node* FindGreaterOrEqual(const Key& key) const; |
154 | | |
155 | | // Return the latest node with a key < key. |
156 | | // Return head_ if there is no such node. |
157 | | // Fills prev[level] with pointer to previous node at "level" for every |
158 | | // level in [0..max_height_-1], if prev is non-null. |
159 | | Node* FindLessThan(const Key& key, Node** prev = nullptr) const; |
160 | | |
161 | | // Return the last node in the list. |
162 | | // Return head_ if list is empty. |
163 | | Node* FindLast() const; |
164 | | }; |
165 | | |
166 | | // Implementation details follow |
167 | | template <typename Key, class Comparator> |
168 | | struct SkipList<Key, Comparator>::Node { |
169 | 0 | explicit Node(const Key& k) : key(k) {}Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node::Node(rocksdb::WriteBatchIndexEntry* const&) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node::Node(char const* const&) |
170 | | |
171 | | Key const key; |
172 | | |
173 | | // Accessors/mutators for links. Wrapped in methods so we can |
174 | | // add the appropriate barriers as necessary. |
175 | 0 | Node* Next(int n) { |
176 | 0 | assert(n >= 0); |
177 | | // Use an 'acquire load' so that we observe a fully initialized |
178 | | // version of the returned Node. |
179 | 0 | return (next_[n].Load()); |
180 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node::Next(int) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node::Next(int) |
181 | 0 | void SetNext(int n, Node* x) { |
182 | 0 | assert(n >= 0); |
183 | | // Use a 'release store' so that anybody who reads through this |
184 | | // pointer observes a fully initialized version of the inserted node. |
185 | 0 | next_[n].Store(x); |
186 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node::SetNext(int, rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node*) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node::SetNext(int, rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node*) |
187 | | |
188 | | // No-barrier variants that can be safely used in a few locations. |
189 | 0 | Node* NoBarrier_Next(int n) { |
190 | 0 | assert(n >= 0); |
191 | 0 | return next_[n].LoadRelaxed(); |
192 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node::NoBarrier_Next(int) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node::NoBarrier_Next(int) |
193 | 0 | void NoBarrier_SetNext(int n, Node* x) { |
194 | 0 | assert(n >= 0); |
195 | 0 | next_[n].StoreRelaxed(x); |
196 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node::NoBarrier_SetNext(int, rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node*) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node::NoBarrier_SetNext(int, rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node*) |
197 | | |
198 | | private: |
199 | | // Array of length equal to the node height. next_[0] is lowest level link. |
200 | | AcqRelAtomic<Node*> next_[1]; |
201 | | }; |
202 | | |
203 | | template <typename Key, class Comparator> |
204 | | typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::NewNode( |
205 | 0 | const Key& key, int height) { |
206 | 0 | char* mem = allocator_->AllocateAligned( |
207 | 0 | sizeof(Node) + sizeof(AcqRelAtomic<Node*>) * (height - 1)); |
208 | 0 | return new (mem) Node(key); |
209 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::NewNode(rocksdb::WriteBatchIndexEntry* const&, int) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::NewNode(char const* const&, int) |
210 | | |
211 | | template <typename Key, class Comparator> |
212 | 0 | inline SkipList<Key, Comparator>::Iterator::Iterator(const SkipList* list) { |
213 | 0 | SetList(list); |
214 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::Iterator(rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&> const*) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::Iterator(rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&> const*) |
215 | | |
216 | | template <typename Key, class Comparator> |
217 | 0 | inline void SkipList<Key, Comparator>::Iterator::SetList(const SkipList* list) { |
218 | 0 | list_ = list; |
219 | 0 | node_ = nullptr; |
220 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::SetList(rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&> const*) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::SetList(rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&> const*) |
221 | | |
222 | | template <typename Key, class Comparator> |
223 | 0 | inline bool SkipList<Key, Comparator>::Iterator::Valid() const { |
224 | 0 | return node_ != nullptr; |
225 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::Valid() const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::Valid() const |
226 | | |
227 | | template <typename Key, class Comparator> |
228 | 0 | inline const Key& SkipList<Key, Comparator>::Iterator::key() const { |
229 | 0 | assert(Valid()); |
230 | 0 | return node_->key; |
231 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::key() const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::key() const |
232 | | |
233 | | template <typename Key, class Comparator> |
234 | 0 | inline void SkipList<Key, Comparator>::Iterator::Next() { |
235 | 0 | assert(Valid()); |
236 | 0 | node_ = node_->Next(0); |
237 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::Next() Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::Next() |
238 | | |
239 | | template <typename Key, class Comparator> |
240 | 0 | inline void SkipList<Key, Comparator>::Iterator::Prev() { |
241 | | // Instead of using explicit "prev" links, we just search for the |
242 | | // last node that falls before key. |
243 | 0 | assert(Valid()); |
244 | 0 | node_ = list_->FindLessThan(node_->key); |
245 | 0 | if (node_ == list_->head_) { |
246 | 0 | node_ = nullptr; |
247 | 0 | } |
248 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::Prev() Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::Prev() |
249 | | |
250 | | template <typename Key, class Comparator> |
251 | 0 | inline void SkipList<Key, Comparator>::Iterator::Seek(const Key& target) { |
252 | 0 | node_ = list_->FindGreaterOrEqual(target); |
253 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::Seek(rocksdb::WriteBatchIndexEntry* const&) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::Seek(char const* const&) |
254 | | |
255 | | template <typename Key, class Comparator> |
256 | | inline void SkipList<Key, Comparator>::Iterator::SeekForPrev( |
257 | 0 | const Key& target) { |
258 | 0 | Seek(target); |
259 | 0 | if (!Valid()) { |
260 | 0 | SeekToLast(); |
261 | 0 | } |
262 | 0 | while (Valid() && list_->LessThan(target, key())) { |
263 | 0 | Prev(); |
264 | 0 | } |
265 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::SeekForPrev(rocksdb::WriteBatchIndexEntry* const&) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::SeekForPrev(char const* const&) |
266 | | |
267 | | template <typename Key, class Comparator> |
268 | 0 | inline void SkipList<Key, Comparator>::Iterator::SeekToFirst() { |
269 | 0 | node_ = list_->head_->Next(0); |
270 | 0 | } |
271 | | |
272 | | template <typename Key, class Comparator> |
273 | 0 | inline void SkipList<Key, Comparator>::Iterator::SeekToLast() { |
274 | 0 | node_ = list_->FindLast(); |
275 | 0 | if (node_ == list_->head_) { |
276 | 0 | node_ = nullptr; |
277 | 0 | } |
278 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Iterator::SeekToLast() Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Iterator::SeekToLast() |
279 | | |
280 | | template <typename Key, class Comparator> |
281 | 0 | int SkipList<Key, Comparator>::RandomHeight() { |
282 | 0 | auto rnd = Random::GetTLSInstance(); |
283 | | |
284 | | // Increase height with probability 1 in kBranching |
285 | 0 | int height = 1; |
286 | 0 | while (height < kMaxHeight_ && rnd->Next() < kScaledInverseBranching_) { |
287 | 0 | height++; |
288 | 0 | } |
289 | 0 | assert(height > 0); |
290 | 0 | assert(height <= kMaxHeight_); |
291 | 0 | return height; |
292 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::RandomHeight() Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::RandomHeight() |
293 | | |
294 | | template <typename Key, class Comparator> |
295 | 0 | bool SkipList<Key, Comparator>::KeyIsAfterNode(const Key& key, Node* n) const { |
296 | | // nullptr n is considered infinite |
297 | 0 | return (n != nullptr) && (compare_(n->key, key) < 0); |
298 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::KeyIsAfterNode(rocksdb::WriteBatchIndexEntry* const&, rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node*) const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::KeyIsAfterNode(char const* const&, rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node*) const |
299 | | |
300 | | template <typename Key, class Comparator> |
301 | | typename SkipList<Key, Comparator>::Node* |
302 | 0 | SkipList<Key, Comparator>::FindGreaterOrEqual(const Key& key) const { |
303 | | // Note: It looks like we could reduce duplication by implementing |
304 | | // this function as FindLessThan(key)->Next(0), but we wouldn't be able |
305 | | // to exit early on equality and the result wouldn't even be correct. |
306 | | // A concurrent insert might occur after FindLessThan(key) but before |
307 | | // we get a chance to call Next(0). |
308 | 0 | Node* x = head_; |
309 | 0 | int level = GetMaxHeight() - 1; |
310 | 0 | Node* last_bigger = nullptr; |
311 | 0 | while (true) { |
312 | 0 | assert(x != nullptr); |
313 | 0 | Node* next = x->Next(level); |
314 | | // Make sure the lists are sorted |
315 | 0 | assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x)); |
316 | | // Make sure we haven't overshot during our search |
317 | 0 | assert(x == head_ || KeyIsAfterNode(key, x)); |
318 | 0 | int cmp = |
319 | 0 | (next == nullptr || next == last_bigger) ? 1 : compare_(next->key, key); |
320 | 0 | if (cmp == 0 || (cmp > 0 && level == 0)) { |
321 | 0 | return next; |
322 | 0 | } else if (cmp < 0) { |
323 | | // Keep searching in this list |
324 | 0 | x = next; |
325 | 0 | } else { |
326 | | // Switch to next list, reuse compare_() result |
327 | 0 | last_bigger = next; |
328 | 0 | level--; |
329 | 0 | } |
330 | 0 | } |
331 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::FindGreaterOrEqual(rocksdb::WriteBatchIndexEntry* const&) const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::FindGreaterOrEqual(char const* const&) const |
332 | | |
333 | | template <typename Key, class Comparator> |
334 | | typename SkipList<Key, Comparator>::Node* |
335 | 0 | SkipList<Key, Comparator>::FindLessThan(const Key& key, Node** prev) const { |
336 | 0 | Node* x = head_; |
337 | 0 | int level = GetMaxHeight() - 1; |
338 | | // KeyIsAfter(key, last_not_after) is definitely false |
339 | 0 | Node* last_not_after = nullptr; |
340 | 0 | while (true) { |
341 | 0 | assert(x != nullptr); |
342 | 0 | Node* next = x->Next(level); |
343 | 0 | assert(x == head_ || next == nullptr || KeyIsAfterNode(next->key, x)); |
344 | 0 | assert(x == head_ || KeyIsAfterNode(key, x)); |
345 | 0 | if (next != last_not_after && KeyIsAfterNode(key, next)) { |
346 | | // Keep searching in this list |
347 | 0 | x = next; |
348 | 0 | } else { |
349 | 0 | if (prev != nullptr) { |
350 | 0 | prev[level] = x; |
351 | 0 | } |
352 | 0 | if (level == 0) { |
353 | 0 | return x; |
354 | 0 | } else { |
355 | | // Switch to next list, reuse KeyIUsAfterNode() result |
356 | 0 | last_not_after = next; |
357 | 0 | level--; |
358 | 0 | } |
359 | 0 | } |
360 | 0 | } |
361 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::FindLessThan(rocksdb::WriteBatchIndexEntry* const&, rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Node**) const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::FindLessThan(char const* const&, rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Node**) const |
362 | | |
363 | | template <typename Key, class Comparator> |
364 | | typename SkipList<Key, Comparator>::Node* SkipList<Key, Comparator>::FindLast() |
365 | 0 | const { |
366 | 0 | Node* x = head_; |
367 | 0 | int level = GetMaxHeight() - 1; |
368 | 0 | while (true) { |
369 | 0 | Node* next = x->Next(level); |
370 | 0 | if (next == nullptr) { |
371 | 0 | if (level == 0) { |
372 | 0 | return x; |
373 | 0 | } else { |
374 | | // Switch to next list |
375 | 0 | level--; |
376 | 0 | } |
377 | 0 | } else { |
378 | 0 | x = next; |
379 | 0 | } |
380 | 0 | } |
381 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::FindLast() const Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::FindLast() const |
382 | | |
383 | | template <typename Key, class Comparator> |
384 | | uint64_t SkipList<Key, Comparator>::ApproximateNumEntries( |
385 | | const Slice& start_ikey, const Slice& end_ikey) const { |
386 | | // See InlineSkipList<Comparator>::ApproximateNumEntries() (copy-paste) |
387 | | Node* lb = head_; |
388 | | Node* ub = nullptr; |
389 | | uint64_t count = 0; |
390 | | for (int level = GetMaxHeight() - 1; level >= 0; level--) { |
391 | | auto sufficient_samples = static_cast<uint64_t>(level) * kBranching_ + 10U; |
392 | | if (count >= sufficient_samples) { |
393 | | // No more counting; apply powers of kBranching and avoid floating point |
394 | | count *= kBranching_; |
395 | | continue; |
396 | | } |
397 | | count = 0; |
398 | | Node* next; |
399 | | // Get a more precise lower bound (for start key) |
400 | | for (;;) { |
401 | | next = lb->Next(level); |
402 | | if (next == ub) { |
403 | | break; |
404 | | } |
405 | | assert(next != nullptr); |
406 | | if (compare_(next->Key(), start_ikey) >= 0) { |
407 | | break; |
408 | | } |
409 | | lb = next; |
410 | | } |
411 | | // Count entries on this level until upper bound (for end key) |
412 | | for (;;) { |
413 | | if (next == ub) { |
414 | | break; |
415 | | } |
416 | | assert(next != nullptr); |
417 | | if (compare_(next->Key(), end_ikey) >= 0) { |
418 | | // Save refined upper bound to potentially save key comparison |
419 | | ub = next; |
420 | | break; |
421 | | } |
422 | | count++; |
423 | | next = next->Next(level); |
424 | | } |
425 | | } |
426 | | return count; |
427 | | } |
428 | | |
429 | | template <typename Key, class Comparator> |
430 | | SkipList<Key, Comparator>::SkipList(const Comparator cmp, Allocator* allocator, |
431 | | int32_t max_height, |
432 | | int32_t branching_factor) |
433 | 0 | : kMaxHeight_(static_cast<uint16_t>(max_height)), |
434 | 0 | kBranching_(static_cast<uint16_t>(branching_factor)), |
435 | 0 | kScaledInverseBranching_((Random::kMaxNext + 1) / kBranching_), |
436 | 0 | compare_(cmp), |
437 | 0 | allocator_(allocator), |
438 | 0 | head_(NewNode({} /* any key will do */, max_height)), |
439 | 0 | max_height_(1), |
440 | 0 | prev_height_(1) { |
441 | 0 | assert(max_height > 0 && kMaxHeight_ == static_cast<uint32_t>(max_height)); |
442 | 0 | assert(branching_factor > 0 && |
443 | 0 | kBranching_ == static_cast<uint32_t>(branching_factor)); |
444 | 0 | assert(kScaledInverseBranching_ > 0); |
445 | | // Allocate the prev_ Node* array, directly from the passed-in allocator. |
446 | | // prev_ does not need to be freed, as its life cycle is tied up with |
447 | | // the allocator as a whole. |
448 | 0 | prev_ = reinterpret_cast<Node**>( |
449 | 0 | allocator_->AllocateAligned(sizeof(Node*) * kMaxHeight_)); |
450 | 0 | for (int i = 0; i < kMaxHeight_; i++) { |
451 | 0 | head_->SetNext(i, nullptr); |
452 | 0 | prev_[i] = head_; |
453 | 0 | } |
454 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::SkipList(rocksdb::WriteBatchEntryComparator const&, rocksdb::Allocator*, int, int) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::SkipList(rocksdb::MemTableRep::KeyComparator const&, rocksdb::Allocator*, int, int) |
455 | | |
456 | | template <typename Key, class Comparator> |
457 | 0 | void SkipList<Key, Comparator>::Insert(const Key& key) { |
458 | | // fast path for sequential insertion |
459 | 0 | if (!KeyIsAfterNode(key, prev_[0]->NoBarrier_Next(0)) && |
460 | 0 | (prev_[0] == head_ || KeyIsAfterNode(key, prev_[0]))) { |
461 | 0 | assert(prev_[0] != head_ || (prev_height_ == 1 && GetMaxHeight() == 1)); |
462 | | |
463 | | // Outside of this method prev_[1..max_height_] is the predecessor |
464 | | // of prev_[0], and prev_height_ refers to prev_[0]. Inside Insert |
465 | | // prev_[0..max_height - 1] is the predecessor of key. Switch from |
466 | | // the external state to the internal |
467 | 0 | for (int i = 1; i < prev_height_; i++) { |
468 | 0 | prev_[i] = prev_[0]; |
469 | 0 | } |
470 | 0 | } else { |
471 | | // TODO(opt): we could use a NoBarrier predecessor search as an |
472 | | // optimization for architectures where memory_order_acquire needs |
473 | | // a synchronization instruction. Doesn't matter on x86 |
474 | 0 | FindLessThan(key, prev_); |
475 | 0 | } |
476 | | |
477 | | // Our data structure does not allow duplicate insertion |
478 | 0 | assert(prev_[0]->Next(0) == nullptr || !Equal(key, prev_[0]->Next(0)->key)); |
479 | |
|
480 | 0 | int height = RandomHeight(); |
481 | 0 | if (height > GetMaxHeight()) { |
482 | 0 | for (int i = GetMaxHeight(); i < height; i++) { |
483 | 0 | prev_[i] = head_; |
484 | 0 | } |
485 | | // fprintf(stderr, "Change height from %d to %d\n", max_height_, height); |
486 | | |
487 | | // It is ok to mutate max_height_ without any synchronization |
488 | | // with concurrent readers. A concurrent reader that observes |
489 | | // the new value of max_height_ will see either the old value of |
490 | | // new level pointers from head_ (nullptr), or a new value set in |
491 | | // the loop below. In the former case the reader will |
492 | | // immediately drop to the next level since nullptr sorts after all |
493 | | // keys. In the latter case the reader will use the new node. |
494 | 0 | max_height_.StoreRelaxed(height); |
495 | 0 | } |
496 | |
|
497 | 0 | Node* x = NewNode(key, height); |
498 | 0 | for (int i = 0; i < height; i++) { |
499 | | // NoBarrier_SetNext() suffices since we will add a barrier when |
500 | | // we publish a pointer to "x" in prev[i]. |
501 | 0 | x->NoBarrier_SetNext(i, prev_[i]->NoBarrier_Next(i)); |
502 | 0 | prev_[i]->SetNext(i, x); |
503 | 0 | } |
504 | 0 | prev_[0] = x; |
505 | 0 | prev_height_ = height; |
506 | 0 | } Unexecuted instantiation: rocksdb::SkipList<rocksdb::WriteBatchIndexEntry*, rocksdb::WriteBatchEntryComparator const&>::Insert(rocksdb::WriteBatchIndexEntry* const&) Unexecuted instantiation: rocksdb::SkipList<char const*, rocksdb::MemTableRep::KeyComparator const&>::Insert(char const* const&) |
507 | | |
508 | | template <typename Key, class Comparator> |
509 | 0 | bool SkipList<Key, Comparator>::Contains(const Key& key) const { |
510 | 0 | Node* x = FindGreaterOrEqual(key); |
511 | 0 | if (x != nullptr && Equal(key, x->key)) { |
512 | 0 | return true; |
513 | 0 | } else { |
514 | 0 | return false; |
515 | 0 | } |
516 | 0 | } |
517 | | |
518 | | } // namespace ROCKSDB_NAMESPACE |