Skip to content

Commit 9d96eff

Browse files
committed
arenaskl: use pointer comparison to avoid key comparison
When inserting a new node into the memtable skiplist, we must determine which nodes within the skiplist will be to the left and right of the new node. When computing this "slice" we descend to each level, moving along the level until we find a key greater than the one we're inserting. Our skiplist implementation has a max height of 20, so we must consider at most 20 levels. With each additional level, the probability a node existing at that level increases by the inverse of Euler's number (> 36%). It's not unlikely that one node may point to the same node on consecutive levels of the skiplist. This commit adapts the splice-finding code to take advantage of this property by checking for pointer equality between next nodes of consecutive levels. If we observe a pointer to a node at height h and we already recorded the node as 'next' within the splice at height h+1, we can elide the key comparison. ``` goos: linux goarch: amd64 pkg: github.com/cockroachdb/pebble/internal/arenaskl cpu: Intel(R) Xeon(R) CPU @ 2.80GHz │ a.txt │ c.txt │ │ sec/op │ sec/op vs base │ ReadWrite/frac_0-24 247.1n ± 2% 236.6n ± 1% -4.23% (p=0.002 n=6) ReadWrite/frac_10-24 233.8n ± 1% 225.5n ± 1% -3.53% (p=0.002 n=6) ReadWrite/frac_20-24 219.7n ± 1% 211.9n ± 0% -3.55% (p=0.002 n=6) ReadWrite/frac_30-24 206.7n ± 1% 199.0n ± 1% -3.70% (p=0.002 n=6) ReadWrite/frac_40-24 192.2n ± 1% 183.8n ± 2% -4.37% (p=0.002 n=6) ReadWrite/frac_50-24 176.2n ± 1% 169.2n ± 2% -3.97% (p=0.002 n=6) ReadWrite/frac_60-24 163.0n ± 2% 157.1n ± 2% -3.62% (p=0.002 n=6) ReadWrite/frac_70-24 143.7n ± 3% 140.7n ± 4% -2.09% (p=0.015 n=6) ReadWrite/frac_80-24 124.6n ± 4% 121.7n ± 6% -2.37% (p=0.048 n=6) ReadWrite/frac_90-24 101.81n ± 6% 93.70n ± 4% -7.97% (p=0.026 n=6) ReadWrite/frac_100-24 10.41n ± 22% 10.45n ± 57% ~ (p=0.818 n=6) OrderedWrite-24 101.5n ± 0% 101.0n ± 0% -0.54% (p=0.004 n=6) geomean 132.0n 127.6n -3.32% ```
1 parent 5e6f0e1 commit 9d96eff

File tree

1 file changed

+42
-0
lines changed

1 file changed

+42
-0
lines changed

internal/arenaskl/skl.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,9 +385,51 @@ func (s *Skiplist) findSplice(key base.InternalKey, ins *Inserter) (found bool)
385385

386386
var next *node
387387
for level = level - 1; level >= 0; level-- {
388+
prevLevelNext := next
389+
388390
for {
389391
// Assume prev.key < key.
390392
next = s.getNext(prev, level)
393+
394+
// Before performing a key comparison, check if the next pointer
395+
// equals prevLevelNext. The pointer comparison is significantly
396+
// cheaper than a key comparison.
397+
//
398+
// It's not unlikely for consecutive levels to have the same next
399+
// pointer. We use [maxHeight]=20 levels, and with each higher
400+
// height the probability a node extends one more rung of the tower
401+
// is 1/e.
402+
//
403+
// The skiplist may contain nodes with keys between the (prev,next)
404+
// pair of nodes that make up the previous level's splice. Let's
405+
// divide these nodes into the L nodes with keys < key and the R
406+
// nodes with keys > key. Only a subset of these nodes may have
407+
// towers that reach [level].
408+
//
409+
// Of the nodes in R that reach [level], we only care about the one
410+
// with the smallest key. If there are no nodes in R that reach
411+
// [level], then this level's splice's next pointer will be the same
412+
// as the level above's splice's next pointer. We can perform a
413+
// cheap pointer comparison of [next] and [prevLevelNext] to
414+
// determine this.
415+
//
416+
// (Note that we must still skip over any of the nodes in L that are
417+
// high enough to reach [level], and each of these nodes will
418+
// require a key comparison.)
419+
//
420+
// (< key) (≥ key)
421+
// prev prevLevelNext
422+
// +---------+ +---------+
423+
// | | | |
424+
// | level+1 |------------------------> | |
425+
// | | | |
426+
// | | next | |
427+
// | | +--------+ | |
428+
// | level |--...--| |--...--> | |
429+
// | | | | | |
430+
if next == prevLevelNext {
431+
break
432+
}
391433
if next == s.tail {
392434
// Tail node, so done.
393435
break

0 commit comments

Comments
 (0)