Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessor methods for skiplist nodes #385

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 67 additions & 58 deletions leveldb/memdb/memdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
package memdb

import (
"fmt"
"math/rand"
"sync"
"unsafe"

"github.com/syndtr/goleveldb/leveldb/comparer"
"github.com/syndtr/goleveldb/leveldb/errors"
Expand All @@ -23,7 +25,10 @@ var (
ErrIterReleased = errors.New("leveldb/memdb: iterator released")
)

const tMaxHeight = 12
const (
tMaxHeight = 12 // max height of a skiplist 'tower'
branching = 4 // branching factor for the skiplist
)

type dbIter struct {
util.BasicReleaser
Expand All @@ -37,9 +42,8 @@ type dbIter struct {

func (i *dbIter) fill(checkStart, checkLimit bool) bool {
if i.node != 0 {
n := i.p.nodeData[i.node]
m := n + i.p.nodeData[i.node+nKey]
i.key = i.p.kvData[n:m]
node := i.p.nodeAt(i.node)
i.key = i.p.kvData[node.kStart():node.kEnd()]
if i.slice != nil {
switch {
case checkLimit && i.slice.Limit != nil && i.p.cmp.Compare(i.key, i.slice.Limit) >= 0:
Expand All @@ -49,7 +53,7 @@ func (i *dbIter) fill(checkStart, checkLimit bool) bool {
goto bail
}
}
i.value = i.p.kvData[m : m+i.p.nodeData[i.node+nVal]]
i.value = i.p.kvData[node.vStart():node.vEnd()]
return true
}
bail:
Expand All @@ -74,7 +78,7 @@ func (i *dbIter) First() bool {
if i.slice != nil && i.slice.Start != nil {
i.node, _ = i.p.findGE(i.slice.Start, false)
} else {
i.node = i.p.nodeData[nNext]
i.node = i.p.nodeAt(0).nextAt(0)
}
return i.fill(false, true)
}
Expand Down Expand Up @@ -127,7 +131,7 @@ func (i *dbIter) Next() bool {
i.forward = true
i.p.mu.RLock()
defer i.p.mu.RUnlock()
i.node = i.p.nodeData[i.node+nNext]
i.node = i.p.nodeAt(i.node).nextAt(0)
return i.fill(false, true)
}

Expand Down Expand Up @@ -170,36 +174,21 @@ func (i *dbIter) Release() {
}
}

const (
nKV = iota
nKey
nVal
nHeight
nNext
)

// DB is an in-memory key/value database.
type DB struct {
cmp comparer.BasicComparer
rnd *rand.Rand

mu sync.RWMutex
kvData []byte
// Node data:
// [0] : KV offset
// [1] : Key length
// [2] : Value length
// [3] : Height
// [3..height] : Next nodes
nodeData []int
mu sync.RWMutex
kvData []byte
nodeData []nodeInt
prevNode [tMaxHeight]int
maxHeight int
n int
kvSize int
}

func (p *DB) randHeight() (h int) {
const branching = 4
h = 1
for h < tMaxHeight && p.rnd.Int()%branching == 0 {
h++
Expand All @@ -212,11 +201,11 @@ func (p *DB) findGE(key []byte, prev bool) (int, bool) {
node := 0
h := p.maxHeight - 1
for {
next := p.nodeData[node+nNext+h]
next := p.nodeAt(node).nextAt(h)
cmp := 1
if next != 0 {
o := p.nodeData[next]
cmp = p.cmp.Compare(p.kvData[o:o+p.nodeData[next+nKey]], key)
o := p.nodeAt(next)
cmp = p.cmp.Compare(p.kvData[o.kStart():o.kEnd()], key)
}
if cmp < 0 {
// Keep searching in this list
Expand All @@ -239,9 +228,9 @@ func (p *DB) findLT(key []byte) int {
node := 0
h := p.maxHeight - 1
for {
next := p.nodeData[node+nNext+h]
o := p.nodeData[next]
if next == 0 || p.cmp.Compare(p.kvData[o:o+p.nodeData[next+nKey]], key) >= 0 {
next := p.nodeAt(node).nextAt(h)
o := p.nodeAt(next)
if next == 0 || p.cmp.Compare(p.kvData[o.kStart():o.kEnd()], key) >= 0 {
if h == 0 {
break
}
Expand All @@ -257,7 +246,7 @@ func (p *DB) findLast() int {
node := 0
h := p.maxHeight - 1
for {
next := p.nodeData[node+nNext+h]
next := p.nodeAt(node).nextAt(h)
if next == 0 {
if h == 0 {
break
Expand All @@ -282,9 +271,10 @@ func (p *DB) Put(key []byte, value []byte) error {
kvOffset := len(p.kvData)
p.kvData = append(p.kvData, key...)
p.kvData = append(p.kvData, value...)
p.nodeData[node] = kvOffset
m := p.nodeData[node+nVal]
p.nodeData[node+nVal] = len(value)
// since match is exact, there's no need to set the key size again
existing := p.nodeAt(node)
m := existing.vLen()
p.nodeAt(node).setKStart(kvOffset).setVLen(len(value))
p.kvSize += len(value) - m
return nil
}
Expand All @@ -302,12 +292,13 @@ func (p *DB) Put(key []byte, value []byte) error {
p.kvData = append(p.kvData, value...)
// Node
node := len(p.nodeData)
p.nodeData = append(p.nodeData, kvOffset, len(key), len(value), h)
newN := newNode(kvOffset, len(key), len(value), h)
for i, n := range p.prevNode[:h] {
m := n + nNext + i
p.nodeData = append(p.nodeData, p.nodeData[m])
p.nodeData[m] = node
prev := p.nodeAt(n)
newN.setNextAt(i, prev.nextAt(i))
prev.setNextAt(i, node)
}
p.nodeData = append(p.nodeData, newN...)

p.kvSize += len(key) + len(value)
p.n++
Expand All @@ -327,13 +318,13 @@ func (p *DB) Delete(key []byte) error {
return ErrNotFound
}

h := p.nodeData[node+nHeight]
todelete := p.nodeAt(node)
h := todelete.height()
for i, n := range p.prevNode[:h] {
m := n + nNext + i
p.nodeData[m] = p.nodeData[p.nodeData[m]+nNext+i]
prev := p.nodeAt(n)
prev.setNextAt(i, todelete.nextAt(i))
}

p.kvSize -= p.nodeData[node+nKey] + p.nodeData[node+nVal]
p.kvSize -= todelete.kLen() + todelete.vLen()
p.n--
return nil
}
Expand All @@ -356,8 +347,8 @@ func (p *DB) Contains(key []byte) bool {
func (p *DB) Get(key []byte) (value []byte, err error) {
p.mu.RLock()
if node, exact := p.findGE(key, false); exact {
o := p.nodeData[node] + p.nodeData[node+nKey]
value = p.kvData[o : o+p.nodeData[node+nVal]]
n := p.nodeAt(node)
value = p.kvData[n.vStart():n.vEnd()]
} else {
err = ErrNotFound
}
Expand All @@ -374,10 +365,9 @@ func (p *DB) Get(key []byte) (value []byte, err error) {
func (p *DB) Find(key []byte) (rkey, value []byte, err error) {
p.mu.RLock()
if node, _ := p.findGE(key, false); node != 0 {
n := p.nodeData[node]
m := n + p.nodeData[node+nKey]
rkey = p.kvData[n:m]
value = p.kvData[m : m+p.nodeData[node+nVal]]
n := p.nodeAt(node)
rkey = p.kvData[n.kStart():n.kEnd()]
value = p.kvData[n.vStart():n.vEnd()]
} else {
err = ErrNotFound
}
Expand Down Expand Up @@ -446,15 +436,15 @@ func (p *DB) Reset() {
p.n = 0
p.kvSize = 0
p.kvData = p.kvData[:0]
p.nodeData = p.nodeData[:nNext+tMaxHeight]
p.nodeData[nKV] = 0
p.nodeData[nKey] = 0
p.nodeData[nVal] = 0
p.nodeData[nHeight] = tMaxHeight

p.nodeData = p.nodeData[:0]
// Add empty first element
zero := newNode(0, 0, 0, tMaxHeight)
for n := 0; n < tMaxHeight; n++ {
p.nodeData[nNext+n] = 0
zero.setNextAt(n, 0)
p.prevNode[n] = 0
}
p.nodeData = append(p.nodeData, zero...)
p.mu.Unlock()
}

Expand All @@ -472,8 +462,27 @@ func New(cmp comparer.BasicComparer, capacity int) *DB {
rnd: rand.New(rand.NewSource(0xdeadbeef)),
maxHeight: 1,
kvData: make([]byte, 0, capacity),
nodeData: make([]int, 4+tMaxHeight),
}
p.nodeData[nHeight] = tMaxHeight
// Add empty first element
zero := newNode(0, 0, 0, tMaxHeight)
for n := 0; n < tMaxHeight; n++ {
zero.setNextAt(n, 0)
}
p.nodeData = append(p.nodeData, zero...)
return p
}

// Stats returns some memdb runtime information.
func (p *DB) Stats() string {
p.mu.RLock()
defer p.mu.RUnlock()
dataSize := len(p.kvData)
metadataSize := len(p.nodeData) * int(unsafe.Sizeof(nodeInt(0)))
return fmt.Sprintf(`keyvalue size: %d
metadata size: %d
item count: %d
data/metadata ratio: %.02f
average kv item size: %.02f
`, dataSize, metadataSize, p.n,
float64(dataSize)/float64(metadataSize+1), float64(dataSize/p.n))
}
14 changes: 6 additions & 8 deletions leveldb/memdb/memdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ import (
func (p *DB) TestFindLT(key []byte) (rkey, value []byte, err error) {
p.mu.RLock()
if node := p.findLT(key); node != 0 {
n := p.nodeData[node]
m := n + p.nodeData[node+nKey]
rkey = p.kvData[n:m]
value = p.kvData[m : m+p.nodeData[node+nVal]]
n := p.nodeAt(node)
rkey = p.kvData[n.kStart():n.kEnd()]
value = p.kvData[n.vStart():n.vEnd()]
} else {
err = ErrNotFound
}
Expand All @@ -33,10 +32,9 @@ func (p *DB) TestFindLT(key []byte) (rkey, value []byte, err error) {
func (p *DB) TestFindLast() (rkey, value []byte, err error) {
p.mu.RLock()
if node := p.findLast(); node != 0 {
n := p.nodeData[node]
m := n + p.nodeData[node+nKey]
rkey = p.kvData[n:m]
value = p.kvData[m : m+p.nodeData[node+nVal]]
n := p.nodeAt(node)
rkey = p.kvData[n.kStart():n.kEnd()]
value = p.kvData[n.vStart():n.vEnd()]
} else {
err = ErrNotFound
}
Expand Down
99 changes: 99 additions & 0 deletions leveldb/memdb/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) 2021, Suryandaru Triandana <[email protected]>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Package memdb provides in-memory key/value database implementation.
package memdb

// The backing representation for the nodeData slice
type nodeInt int

// node represents a node in the skiplist. It maps directly onto the nodeData
// backing array, and is not meant to be used as a separate entity (aside for when
// creating a new one).
// Thus, it's perfectly fine if the underlying array is overly large (since the exact size
// is not known before reading the height).
// Node data is laid out as follows:
// [0] : KV offset
// [1] : Key length
// [2] : Value length
// [3] : Height
// [3..height] : Next nodes
type node []nodeInt

// newNode constructs a new node. Be careful -- this allocates a new slice,
// along with space to store the next, according to the height given.
// This node later needs to be written to the backing slice, making the original
// instance moot.
func newNode(kvOffset, kLen, vLen, height int) node {
buf := make([]nodeInt, 4+height)
buf[0] = nodeInt(kvOffset)
buf[1] = nodeInt(kLen)
buf[2] = nodeInt(vLen)
buf[3] = nodeInt(height)
return node(buf)
}

// kStart returns the start index for the key.
func (n node) kStart() int {
return int(n[0])
}

// kEnd returns the start + length for the key.
func (n node) kEnd() int {
return int(n[0] + n[1])
}

// kLen return the key length.
func (n node) kLen() int {
return int(n[1])
}

// vStart return the offset for the value.
func (n node) vStart() int {
return int(n[0] + n[1])
}

// vEnd return the offset + length for value.
func (n node) vEnd() int {
return int(n[0] + n[1] + n[2])
}

// vLen returns the value length.
func (n node) vLen() int {
return int(n[2])
}

// setKStart sets the key offset.
func (n node) setKStart(keyOffset int) node {
n[0] = nodeInt(keyOffset)
return n
}

// setVLen sets the value length.
func (n node) setVLen(size int) node {
n[2] = nodeInt(size)
return n
}

// height return the size of the next-tower.
func (n node) height() int {
return int(n[3])
}

// nextAt return the item at the given height.
func (n node) nextAt(height int) int {
return int(n[4+height])
}

// setNextAt sets the next item at the given height
func (n node) setNextAt(height int, node int) {
n[4+height] = nodeInt(node)
}

// nodeAt returns the node at the given index.
func (p *DB) nodeAt(idx int) node {
return node(p.nodeData[idx:])
}