Skip to content

Commit 59c56ee

Browse files
authored
feat(skiplist): Add sorted skiplist builder (#1693)
Add a Builder type in skiplist package which can be used to insert sorted keys efficiently. Add a test and benchmark for it.
1 parent 707978d commit 59c56ee

File tree

3 files changed

+127
-9
lines changed

3 files changed

+127
-9
lines changed

skl/arena.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ func (s *Arena) putNode(height int) uint32 {
6464
// Pad the allocation with enough bytes to ensure pointer alignment.
6565
l := uint32(MaxNodeSize - unusedSize + nodeAlign)
6666
n := atomic.AddUint32(&s.n, l)
67-
y.AssertTruef(int(n) <= len(s.buf),
68-
"Arena too small, toWrite:%d newTotal:%d limit:%d",
69-
l, n, len(s.buf))
67+
y.AssertTrue(int(n) <= len(s.buf))
7068

7169
// Return the aligned offset.
7270
m := (n - l + uint32(nodeAlign)) & ^uint32(nodeAlign)
@@ -80,9 +78,7 @@ func (s *Arena) putNode(height int) uint32 {
8078
func (s *Arena) putVal(v y.ValueStruct) uint32 {
8179
l := uint32(v.EncodedSize())
8280
n := atomic.AddUint32(&s.n, l)
83-
y.AssertTruef(int(n) <= len(s.buf),
84-
"Arena too small, toWrite:%d newTotal:%d limit:%d",
85-
l, n, len(s.buf))
81+
y.AssertTrue(int(n) <= len(s.buf))
8682
m := n - l
8783
v.Encode(s.buf[m:])
8884
return m
@@ -91,9 +87,7 @@ func (s *Arena) putVal(v y.ValueStruct) uint32 {
9187
func (s *Arena) putKey(key []byte) uint32 {
9288
l := uint32(len(key))
9389
n := atomic.AddUint32(&s.n, l)
94-
y.AssertTruef(int(n) <= len(s.buf),
95-
"Arena too small, toWrite:%d newTotal:%d limit:%d",
96-
l, n, len(s.buf))
90+
y.AssertTrue(int(n) <= len(s.buf))
9791
// m is the offset where you should write.
9892
// n = new len - key len give you the offset at which you should write.
9993
m := n - l

skl/skl.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Key differences:
3333
package skl
3434

3535
import (
36+
"fmt"
3637
"math"
3738
"sync/atomic"
3839
"unsafe"
@@ -522,3 +523,46 @@ func (s *UniIterator) Valid() bool { return s.iter.Valid() }
522523

523524
// Close implements y.Interface (and frees up the iter's resources)
524525
func (s *UniIterator) Close() error { return s.iter.Close() }
526+
527+
// Builder can be used to efficiently create a skiplist given that the keys are known to be in a
528+
// sorted order.
529+
type Builder struct {
530+
s *Skiplist
531+
prev [maxHeight + 1]*node
532+
prevKey []byte
533+
}
534+
535+
func NewBuilder(arenaSize int64) *Builder {
536+
s := NewSkiplist(arenaSize)
537+
b := &Builder{s: s}
538+
for i := 0; i < maxHeight+1; i++ {
539+
b.prev[i] = s.head
540+
}
541+
return b
542+
}
543+
544+
const debug = false
545+
546+
// Add must be used to add keys in a sorted order.
547+
func (b *Builder) Add(k []byte, v y.ValueStruct) {
548+
if debug {
549+
if len(b.prevKey) > 0 && y.CompareKeys(k, b.prevKey) <= 0 {
550+
panic(fmt.Sprintf("new key: %s <= prev key: %s\n",
551+
y.ParseKey(k), y.ParseKey(b.prevKey)))
552+
}
553+
b.prevKey = append(b.prevKey[:0], k...)
554+
}
555+
s := b.s
556+
height := s.randomHeight()
557+
if int32(height) > s.height {
558+
s.height = int32(height)
559+
}
560+
561+
x := newNode(s.arena, k, v, height)
562+
nodeOffset := s.arena.getNodeOffset(x)
563+
for i := 0; i < height; i++ {
564+
node := b.prev[i]
565+
node.tower[i] = nodeOffset
566+
b.prev[i] = x
567+
}
568+
}

skl/skl_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package skl
1818

1919
import (
20+
"bytes"
2021
"encoding/binary"
2122
"fmt"
2223
"math/rand"
@@ -457,6 +458,39 @@ func randomKey(rng *rand.Rand) []byte {
457458
return y.KeyWithTs(b, 0)
458459
}
459460

461+
func TestBuilder(t *testing.T) {
462+
N := 1 << 16
463+
b := NewBuilder(32 << 20)
464+
buf := make([]byte, 8)
465+
for i := 0; i < N; i++ {
466+
binary.BigEndian.PutUint64(buf, uint64(i))
467+
key := y.KeyWithTs(buf, 0)
468+
b.Add(key, y.ValueStruct{Value: []byte("00072")})
469+
}
470+
sl := b.s
471+
for i := 0; i < N; i++ {
472+
binary.BigEndian.PutUint64(buf, uint64(i))
473+
key := y.KeyWithTs(buf, 0)
474+
v := sl.Get(key)
475+
require.NotNil(t, v.Value)
476+
require.EqualValues(t, "00072", string(v.Value))
477+
}
478+
it := sl.NewIterator()
479+
defer it.Close()
480+
481+
require.False(t, it.Valid())
482+
it.SeekToFirst()
483+
i := 0
484+
for it.Valid() {
485+
binary.BigEndian.PutUint64(buf, uint64(i))
486+
key := y.KeyWithTs(buf, 0)
487+
require.Equal(t, key, it.Key())
488+
it.Next()
489+
i++
490+
}
491+
require.Equal(t, N, i)
492+
}
493+
460494
// Standard test. Some fraction is read. Some fraction is write. Writes have
461495
// to go through mutex lock.
462496
func BenchmarkReadWrite(b *testing.B) {
@@ -529,3 +563,49 @@ func BenchmarkWrite(b *testing.B) {
529563
}
530564
})
531565
}
566+
567+
// $ go test -run=XXX -v -bench BenchmarkSortedWrites
568+
//
569+
// BenchmarkSortedWrites/builder-8 11298607 106 ns/op
570+
// BenchmarkSortedWrites/skiplist-8 2523454 495 ns/op
571+
// BenchmarkSortedWrites/buffer-8 10377798 108 ns/op
572+
573+
func BenchmarkSortedWrites(b *testing.B) {
574+
b.Run("builder", func(b *testing.B) {
575+
bl := NewBuilder(int64((b.N + 1) * MaxNodeSize))
576+
buf := make([]byte, 8)
577+
b.ResetTimer()
578+
for i := 0; i < b.N; i++ {
579+
binary.BigEndian.PutUint64(buf, uint64(i))
580+
key := y.KeyWithTs(buf, 0)
581+
bl.Add(key, y.ValueStruct{Value: []byte("00072")})
582+
}
583+
})
584+
585+
b.Run("skiplist", func(b *testing.B) {
586+
bl := NewSkiplist(int64((b.N + 1) * MaxNodeSize))
587+
buf := make([]byte, 8)
588+
b.ResetTimer()
589+
for i := 0; i < b.N; i++ {
590+
binary.BigEndian.PutUint64(buf, uint64(i))
591+
key := y.KeyWithTs(buf, 0)
592+
bl.Put(key, y.ValueStruct{Value: []byte("00072")})
593+
}
594+
})
595+
596+
b.Run("buffer", func(b *testing.B) {
597+
var bl bytes.Buffer
598+
buf := make([]byte, 8)
599+
b.ResetTimer()
600+
for i := 0; i < b.N; i++ {
601+
binary.BigEndian.PutUint64(buf, uint64(i))
602+
key := y.KeyWithTs(buf, 0)
603+
v := y.ValueStruct{Value: []byte("00072")}
604+
vbuf := make([]byte, v.EncodedSize())
605+
v.Encode(vbuf)
606+
607+
kv := append(key, vbuf...)
608+
bl.Write(kv)
609+
}
610+
})
611+
}

0 commit comments

Comments
 (0)