Initial commit
This commit is contained in:
56
vendor/github.com/cockroachdb/pebble/internal/batchskl/README.md
generated
vendored
Normal file
56
vendor/github.com/cockroachdb/pebble/internal/batchskl/README.md
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# batchskl
|
||||
|
||||
Fast, non-concurrent skiplist implementation in Go that supports
|
||||
forward and backward iteration.
|
||||
|
||||
## Limitations
|
||||
|
||||
* The interface is tailored for use in indexing pebble batches. Keys
|
||||
and values are stored outside of the skiplist making the skiplist
|
||||
awkward for general purpose use.
|
||||
* Deletion is not supported. Instead, higher-level code is expected to
|
||||
add deletion tombstones and needs to process those tombstones
|
||||
appropriately.
|
||||
|
||||
## Pedigree
|
||||
|
||||
This code is based on Andy Kimball's arenaskl code.
|
||||
|
||||
The arenaskl code is based on the skiplist found in Badger, a Go-based
|
||||
KV store:
|
||||
|
||||
https://github.com/dgraph-io/badger/tree/master/skl
|
||||
|
||||
The skiplist in Badger is itself based on a C++ skiplist built for
|
||||
Facebook's RocksDB:
|
||||
|
||||
https://github.com/facebook/rocksdb/tree/master/memtable
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The benchmarks consist of a mix of reads and writes executed in parallel. The
|
||||
fraction of reads is indicated in the run name: "frac_X" indicates a run where
|
||||
X percent of the operations are reads.
|
||||
|
||||
```
|
||||
name time/op
|
||||
ReadWrite/frac_0 1.03µs ± 2%
|
||||
ReadWrite/frac_10 1.32µs ± 1%
|
||||
ReadWrite/frac_20 1.26µs ± 1%
|
||||
ReadWrite/frac_30 1.18µs ± 1%
|
||||
ReadWrite/frac_40 1.09µs ± 1%
|
||||
ReadWrite/frac_50 987ns ± 2%
|
||||
ReadWrite/frac_60 1.07µs ± 1%
|
||||
ReadWrite/frac_70 909ns ± 1%
|
||||
ReadWrite/frac_80 693ns ± 2%
|
||||
ReadWrite/frac_90 599ns ± 2%
|
||||
ReadWrite/frac_100 45.3ns ± 3%
|
||||
```
|
||||
|
||||
Forward and backward iteration are also fast:
|
||||
|
||||
```
|
||||
name time/op
|
||||
IterNext 4.49ns ± 3%
|
||||
IterPrev 4.48ns ± 3%
|
||||
```
|
||||
223
vendor/github.com/cockroachdb/pebble/internal/batchskl/iterator.go
generated
vendored
Normal file
223
vendor/github.com/cockroachdb/pebble/internal/batchskl/iterator.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright 2017 Dgraph Labs, Inc. and Contributors
|
||||
* Modifications copyright (C) 2017 Andy Kimball and Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package batchskl
|
||||
|
||||
import "github.com/cockroachdb/pebble/internal/base"
|
||||
|
||||
type splice struct {
|
||||
prev uint32
|
||||
next uint32
|
||||
}
|
||||
|
||||
// Iterator is an iterator over the skiplist object. Use Skiplist.NewIter
|
||||
// to construct an iterator. The current state of the iterator can be cloned
|
||||
// by simply value copying the struct.
|
||||
type Iterator struct {
|
||||
list *Skiplist
|
||||
nd uint32
|
||||
key base.InternalKey
|
||||
lower []byte
|
||||
upper []byte
|
||||
}
|
||||
|
||||
// Close resets the iterator.
|
||||
func (it *Iterator) Close() error {
|
||||
it.list = nil
|
||||
it.nd = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// SeekGE moves the iterator to the first entry whose key is greater than or
|
||||
// equal to the given key. Returns true if the iterator is pointing at a valid
|
||||
// entry and false otherwise. Note that SeekGE only checks the upper bound. It
|
||||
// is up to the caller to ensure that key is greater than or equal to the lower
|
||||
// bound.
|
||||
func (it *Iterator) SeekGE(key []byte, flags base.SeekGEFlags) *base.InternalKey {
|
||||
if flags.TrySeekUsingNext() {
|
||||
if it.nd == it.list.tail {
|
||||
// Iterator is done.
|
||||
return nil
|
||||
}
|
||||
less := it.list.cmp(it.key.UserKey, key) < 0
|
||||
// Arbitrary constant. By measuring the seek cost as a function of the
|
||||
// number of elements in the skip list, and fitting to a model, we
|
||||
// could adjust the number of nexts based on the current size of the
|
||||
// skip list.
|
||||
const numNexts = 5
|
||||
for i := 0; less && i < numNexts; i++ {
|
||||
k := it.Next()
|
||||
if k == nil {
|
||||
// Iterator is done.
|
||||
return nil
|
||||
}
|
||||
less = it.list.cmp(k.UserKey, key) < 0
|
||||
}
|
||||
if !less {
|
||||
return &it.key
|
||||
}
|
||||
}
|
||||
|
||||
_, it.nd = it.seekForBaseSplice(key, it.list.abbreviatedKey(key))
|
||||
if it.nd == it.list.tail {
|
||||
return nil
|
||||
}
|
||||
nodeKey := it.list.getKey(it.nd)
|
||||
if it.upper != nil && it.list.cmp(it.upper, nodeKey.UserKey) <= 0 {
|
||||
it.nd = it.list.tail
|
||||
return nil
|
||||
}
|
||||
it.key = nodeKey
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// SeekLT moves the iterator to the last entry whose key is less the given
|
||||
// key. Returns true if the iterator is pointing at a valid entry and false
|
||||
// otherwise. Note that SeekLT only checks the lower bound. It is up to the
|
||||
// caller to ensure that key is less than the upper bound.
|
||||
func (it *Iterator) SeekLT(key []byte) *base.InternalKey {
|
||||
it.nd, _ = it.seekForBaseSplice(key, it.list.abbreviatedKey(key))
|
||||
if it.nd == it.list.head {
|
||||
return nil
|
||||
}
|
||||
nodeKey := it.list.getKey(it.nd)
|
||||
if it.lower != nil && it.list.cmp(it.lower, nodeKey.UserKey) > 0 {
|
||||
it.nd = it.list.head
|
||||
return nil
|
||||
}
|
||||
it.key = nodeKey
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// First seeks position at the first entry in list. Final state of iterator is
|
||||
// Valid() iff list is not empty. Note that First only checks the upper
|
||||
// bound. It is up to the caller to ensure that key is greater than or equal to
|
||||
// the lower bound (e.g. via a call to SeekGE(lower)).
|
||||
func (it *Iterator) First() *base.InternalKey {
|
||||
it.nd = it.list.getNext(it.list.head, 0)
|
||||
if it.nd == it.list.tail {
|
||||
return nil
|
||||
}
|
||||
nodeKey := it.list.getKey(it.nd)
|
||||
if it.upper != nil && it.list.cmp(it.upper, nodeKey.UserKey) <= 0 {
|
||||
it.nd = it.list.tail
|
||||
return nil
|
||||
}
|
||||
it.key = nodeKey
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// Last seeks position at the last entry in list. Final state of iterator is
|
||||
// Valid() iff list is not empty. Note that Last only checks the lower
|
||||
// bound. It is up to the caller to ensure that key is less than the upper
|
||||
// bound (e.g. via a call to SeekLT(upper)).
|
||||
func (it *Iterator) Last() *base.InternalKey {
|
||||
it.nd = it.list.getPrev(it.list.tail, 0)
|
||||
if it.nd == it.list.head {
|
||||
return nil
|
||||
}
|
||||
nodeKey := it.list.getKey(it.nd)
|
||||
if it.lower != nil && it.list.cmp(it.lower, nodeKey.UserKey) > 0 {
|
||||
it.nd = it.list.head
|
||||
return nil
|
||||
}
|
||||
it.key = nodeKey
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// Next advances to the next position. If there are no following nodes, then
|
||||
// Valid() will be false after this call.
|
||||
func (it *Iterator) Next() *base.InternalKey {
|
||||
it.nd = it.list.getNext(it.nd, 0)
|
||||
if it.nd == it.list.tail {
|
||||
return nil
|
||||
}
|
||||
nodeKey := it.list.getKey(it.nd)
|
||||
if it.upper != nil && it.list.cmp(it.upper, nodeKey.UserKey) <= 0 {
|
||||
it.nd = it.list.tail
|
||||
return nil
|
||||
}
|
||||
it.key = nodeKey
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// Prev moves to the previous position. If there are no previous nodes, then
|
||||
// Valid() will be false after this call.
|
||||
func (it *Iterator) Prev() *base.InternalKey {
|
||||
it.nd = it.list.getPrev(it.nd, 0)
|
||||
if it.nd == it.list.head {
|
||||
return nil
|
||||
}
|
||||
nodeKey := it.list.getKey(it.nd)
|
||||
if it.lower != nil && it.list.cmp(it.lower, nodeKey.UserKey) > 0 {
|
||||
it.nd = it.list.head
|
||||
return nil
|
||||
}
|
||||
it.key = nodeKey
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// Key returns the key at the current position.
|
||||
func (it *Iterator) Key() *base.InternalKey {
|
||||
return &it.key
|
||||
}
|
||||
|
||||
// KeyInfo returns the offset of the start of the record, the start of the key,
|
||||
// and the end of the key.
|
||||
func (it *Iterator) KeyInfo() (offset, keyStart, keyEnd uint32) {
|
||||
n := it.list.node(it.nd)
|
||||
return n.offset, n.keyStart, n.keyEnd
|
||||
}
|
||||
|
||||
// Head true iff the iterator is positioned at the sentinel head node.
|
||||
func (it *Iterator) Head() bool {
|
||||
return it.nd == it.list.head
|
||||
}
|
||||
|
||||
// Tail true iff the iterator is positioned at the sentinel tail node.
|
||||
func (it *Iterator) Tail() bool {
|
||||
return it.nd == it.list.tail
|
||||
}
|
||||
|
||||
// Valid returns nil iff the iterator is positioned at a valid node.
|
||||
func (it *Iterator) Valid() bool {
|
||||
return it.list != nil && it.nd != it.list.head && it.nd != it.list.tail
|
||||
}
|
||||
|
||||
func (it *Iterator) String() string {
|
||||
return "batch"
|
||||
}
|
||||
|
||||
// SetBounds sets the lower and upper bounds for the iterator. Note that the
|
||||
// result of Next and Prev will be undefined until the iterator has been
|
||||
// repositioned with SeekGE, SeekLT, First, or Last.
|
||||
func (it *Iterator) SetBounds(lower, upper []byte) {
|
||||
it.lower = lower
|
||||
it.upper = upper
|
||||
}
|
||||
|
||||
func (it *Iterator) seekForBaseSplice(key []byte, abbreviatedKey uint64) (prev, next uint32) {
|
||||
prev = it.list.head
|
||||
for level := it.list.height - 1; ; level-- {
|
||||
prev, next = it.list.findSpliceForLevel(key, abbreviatedKey, level, prev)
|
||||
if level == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
442
vendor/github.com/cockroachdb/pebble/internal/batchskl/skl.go
generated
vendored
Normal file
442
vendor/github.com/cockroachdb/pebble/internal/batchskl/skl.go
generated
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
/*
|
||||
* Copyright 2017 Dgraph Labs, Inc. and Contributors
|
||||
* Modifications copyright (C) 2017 Andy Kimball and Contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License")
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Adapted from RocksDB inline skiplist.
|
||||
|
||||
Key differences:
|
||||
- No optimization for sequential inserts (no "prev").
|
||||
- No custom comparator.
|
||||
- Support overwrites. This requires care when we see the same key when inserting.
|
||||
For RocksDB or LevelDB, overwrites are implemented as a newer sequence number in the key, so
|
||||
there is no need for values. We don't intend to support versioning. In-place updates of values
|
||||
would be more efficient.
|
||||
- We discard all non-concurrent code.
|
||||
- We do not support Splices. This simplifies the code a lot.
|
||||
- No AllocateNode or other pointer arithmetic.
|
||||
- We combine the findLessThan, findGreaterOrEqual, etc into one function.
|
||||
*/
|
||||
|
||||
/*
|
||||
Further adapted from Badger: https://github.com/dgraph-io/badger.
|
||||
|
||||
Key differences:
|
||||
- Support for previous pointers - doubly linked lists. Note that it's up to higher
|
||||
level code to deal with the intermediate state that occurs during insertion,
|
||||
where node A is linked to node B, but node B is not yet linked back to node A.
|
||||
- Iterator includes mutator functions.
|
||||
*/
|
||||
|
||||
/*
|
||||
Further adapted from arenaskl: https://github.com/andy-kimball/arenaskl
|
||||
|
||||
Key differences:
|
||||
- Removed support for deletion.
|
||||
- Removed support for concurrency.
|
||||
- External storage of keys.
|
||||
- Node storage grows to an arbitrary size.
|
||||
*/
|
||||
|
||||
package batchskl // import "github.com/cockroachdb/pebble/internal/batchskl"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/cockroachdb/pebble/internal/base"
|
||||
"github.com/cockroachdb/pebble/internal/constants"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
maxHeight = 20
|
||||
maxNodeSize = uint64(unsafe.Sizeof(node{}))
|
||||
linksSize = uint64(unsafe.Sizeof(links{}))
|
||||
maxNodesSize = constants.MaxUint32OrInt
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrExists indicates that a duplicate record was inserted. This should never
|
||||
// happen for normal usage of batchskl as every key should have a unique
|
||||
// sequence number.
|
||||
ErrExists = errors.New("record with this key already exists")
|
||||
|
||||
// ErrTooManyRecords is a sentinel error returned when the size of the raw
|
||||
// nodes slice exceeds the maximum allowed size (currently 1 << 32 - 1). This
|
||||
// corresponds to ~117 M skiplist entries.
|
||||
ErrTooManyRecords = errors.New("too many records")
|
||||
)
|
||||
|
||||
type links struct {
|
||||
next uint32
|
||||
prev uint32
|
||||
}
|
||||
|
||||
type node struct {
|
||||
// The offset of the start of the record in the storage.
|
||||
offset uint32
|
||||
// The offset of the start and end of the key in storage.
|
||||
keyStart uint32
|
||||
keyEnd uint32
|
||||
// A fixed 8-byte abbreviation of the key, used to avoid retrieval of the key
|
||||
// during seek operations. The key retrieval can be expensive purely due to
|
||||
// cache misses while the abbreviatedKey stored here will be in the same
|
||||
// cache line as the key and the links making accessing and comparing against
|
||||
// it almost free.
|
||||
abbreviatedKey uint64
|
||||
// Most nodes do not need to use the full height of the link tower, since the
|
||||
// probability of each successive level decreases exponentially. Because
|
||||
// these elements are never accessed, they do not need to be allocated.
|
||||
// Therefore, when a node is allocated, its memory footprint is deliberately
|
||||
// truncated to not include unneeded link elements.
|
||||
links [maxHeight]links
|
||||
}
|
||||
|
||||
// Skiplist is a fast, non-cocnurrent skiplist implementation that supports
|
||||
// forward and backward iteration. See arenaskl.Skiplist for a concurrent
|
||||
// skiplist. Keys and values are stored externally from the skiplist via the
|
||||
// Storage interface. Deletion is not supported. Instead, higher-level code is
|
||||
// expected to perform deletion via tombstones and needs to process those
|
||||
// tombstones appropriately during retrieval operations.
|
||||
type Skiplist struct {
|
||||
storage *[]byte
|
||||
cmp base.Compare
|
||||
abbreviatedKey base.AbbreviatedKey
|
||||
nodes []byte
|
||||
head uint32
|
||||
tail uint32
|
||||
height uint32 // Current height: 1 <= height <= maxHeight
|
||||
rand rand.PCGSource
|
||||
}
|
||||
|
||||
var (
|
||||
probabilities [maxHeight]uint32
|
||||
)
|
||||
|
||||
func init() {
|
||||
const pValue = 1 / math.E
|
||||
|
||||
// Precompute the skiplist probabilities so that only a single random number
|
||||
// needs to be generated and so that the optimal pvalue can be used (inverse
|
||||
// of Euler's number).
|
||||
p := float64(1.0)
|
||||
for i := 0; i < maxHeight; i++ {
|
||||
probabilities[i] = uint32(float64(math.MaxUint32) * p)
|
||||
p *= pValue
|
||||
}
|
||||
}
|
||||
|
||||
// NewSkiplist constructs and initializes a new, empty skiplist.
|
||||
func NewSkiplist(storage *[]byte, cmp base.Compare, abbreviatedKey base.AbbreviatedKey) *Skiplist {
|
||||
s := &Skiplist{}
|
||||
s.Init(storage, cmp, abbreviatedKey)
|
||||
return s
|
||||
}
|
||||
|
||||
// Reset the fields in the skiplist for reuse.
|
||||
func (s *Skiplist) Reset() {
|
||||
*s = Skiplist{
|
||||
nodes: s.nodes[:0],
|
||||
height: 1,
|
||||
}
|
||||
const batchMaxRetainedSize = 1 << 20 // 1 MB
|
||||
if cap(s.nodes) > batchMaxRetainedSize {
|
||||
s.nodes = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Init the skiplist to empty and re-initialize.
|
||||
func (s *Skiplist) Init(storage *[]byte, cmp base.Compare, abbreviatedKey base.AbbreviatedKey) {
|
||||
*s = Skiplist{
|
||||
storage: storage,
|
||||
cmp: cmp,
|
||||
abbreviatedKey: abbreviatedKey,
|
||||
nodes: s.nodes[:0],
|
||||
height: 1,
|
||||
}
|
||||
s.rand.Seed(uint64(time.Now().UnixNano()))
|
||||
|
||||
const initBufSize = 256
|
||||
if cap(s.nodes) < initBufSize {
|
||||
s.nodes = make([]byte, 0, initBufSize)
|
||||
}
|
||||
|
||||
// Allocate head and tail nodes. While allocating a new node can fail, in the
|
||||
// context of initializing the skiplist we consider it unrecoverable.
|
||||
var err error
|
||||
s.head, err = s.newNode(maxHeight, 0, 0, 0, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s.tail, err = s.newNode(maxHeight, 0, 0, 0, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Link all head/tail levels together.
|
||||
headNode := s.node(s.head)
|
||||
tailNode := s.node(s.tail)
|
||||
for i := uint32(0); i < maxHeight; i++ {
|
||||
headNode.links[i].next = s.tail
|
||||
tailNode.links[i].prev = s.head
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a new key to the skiplist if it does not yet exist. If the record
|
||||
// already exists, then Add returns ErrRecordExists.
|
||||
func (s *Skiplist) Add(keyOffset uint32) error {
|
||||
data := (*s.storage)[keyOffset+1:]
|
||||
v, n := binary.Uvarint(data)
|
||||
if n <= 0 {
|
||||
return errors.Errorf("corrupted batch entry: %d", errors.Safe(keyOffset))
|
||||
}
|
||||
data = data[n:]
|
||||
if v > uint64(len(data)) {
|
||||
return errors.Errorf("corrupted batch entry: %d", errors.Safe(keyOffset))
|
||||
}
|
||||
keyStart := 1 + keyOffset + uint32(n)
|
||||
keyEnd := keyStart + uint32(v)
|
||||
key := data[:v]
|
||||
abbreviatedKey := s.abbreviatedKey(key)
|
||||
|
||||
// spl holds the list of next and previous links for each level in the
|
||||
// skiplist indicating where the new node will be inserted.
|
||||
var spl [maxHeight]splice
|
||||
|
||||
// Fast-path for in-order insertion of keys: compare the new key against the
|
||||
// last key.
|
||||
prev := s.getPrev(s.tail, 0)
|
||||
if prevNode := s.node(prev); prev == s.head ||
|
||||
abbreviatedKey > prevNode.abbreviatedKey ||
|
||||
(abbreviatedKey == prevNode.abbreviatedKey &&
|
||||
s.cmp(key, (*s.storage)[prevNode.keyStart:prevNode.keyEnd]) > 0) {
|
||||
for level := uint32(0); level < s.height; level++ {
|
||||
spl[level].prev = s.getPrev(s.tail, level)
|
||||
spl[level].next = s.tail
|
||||
}
|
||||
} else {
|
||||
s.findSplice(key, abbreviatedKey, &spl)
|
||||
}
|
||||
|
||||
height := s.randomHeight()
|
||||
// Increase s.height as necessary.
|
||||
for ; s.height < height; s.height++ {
|
||||
spl[s.height].next = s.tail
|
||||
spl[s.height].prev = s.head
|
||||
}
|
||||
|
||||
// We always insert from the base level and up. After you add a node in base
|
||||
// level, we cannot create a node in the level above because it would have
|
||||
// discovered the node in the base level.
|
||||
nd, err := s.newNode(height, keyOffset, keyStart, keyEnd, abbreviatedKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newNode := s.node(nd)
|
||||
for level := uint32(0); level < height; level++ {
|
||||
next := spl[level].next
|
||||
prev := spl[level].prev
|
||||
newNode.links[level].next = next
|
||||
newNode.links[level].prev = prev
|
||||
s.node(next).links[level].prev = nd
|
||||
s.node(prev).links[level].next = nd
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIter returns a new Iterator object. The lower and upper bound parameters
|
||||
// control the range of keys the iterator will return. Specifying for nil for
|
||||
// lower or upper bound disables the check for that boundary. Note that lower
|
||||
// bound is not checked on {SeekGE,First} and upper bound is not check on
|
||||
// {SeekLT,Last}. The user is expected to perform that check. Note that it is
|
||||
// safe for an iterator to be copied by value.
|
||||
func (s *Skiplist) NewIter(lower, upper []byte) Iterator {
|
||||
return Iterator{list: s, lower: lower, upper: upper}
|
||||
}
|
||||
|
||||
func (s *Skiplist) newNode(
|
||||
height,
|
||||
offset, keyStart, keyEnd uint32, abbreviatedKey uint64,
|
||||
) (uint32, error) {
|
||||
if height < 1 || height > maxHeight {
|
||||
panic("height cannot be less than one or greater than the max height")
|
||||
}
|
||||
|
||||
unusedSize := uint64(maxHeight-int(height)) * linksSize
|
||||
nodeOffset, err := s.alloc(uint32(maxNodeSize - unusedSize))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
nd := s.node(nodeOffset)
|
||||
|
||||
nd.offset = offset
|
||||
nd.keyStart = keyStart
|
||||
nd.keyEnd = keyEnd
|
||||
nd.abbreviatedKey = abbreviatedKey
|
||||
return nodeOffset, nil
|
||||
}
|
||||
|
||||
func (s *Skiplist) alloc(size uint32) (uint32, error) {
|
||||
offset := uint64(len(s.nodes))
|
||||
|
||||
// We only have a need for memory up to offset + size, but we never want
|
||||
// to allocate a node whose tail points into unallocated memory.
|
||||
minAllocSize := offset + maxNodeSize
|
||||
if uint64(cap(s.nodes)) < minAllocSize {
|
||||
allocSize := uint64(cap(s.nodes)) * 2
|
||||
if allocSize < minAllocSize {
|
||||
allocSize = minAllocSize
|
||||
}
|
||||
// Cap the allocation at the max allowed size to avoid wasted capacity.
|
||||
if allocSize > maxNodesSize {
|
||||
// The new record may still not fit within the allocation, in which case
|
||||
// we return early with an error. This avoids the panic below when we
|
||||
// resize the slice. It also avoids the allocation and copy.
|
||||
if uint64(offset)+uint64(size) > maxNodesSize {
|
||||
return 0, errors.Wrapf(ErrTooManyRecords,
|
||||
"alloc of new record (size=%d) would overflow uint32 (current size=%d)",
|
||||
uint64(offset)+uint64(size), offset,
|
||||
)
|
||||
}
|
||||
allocSize = maxNodesSize
|
||||
}
|
||||
tmp := make([]byte, len(s.nodes), allocSize)
|
||||
copy(tmp, s.nodes)
|
||||
s.nodes = tmp
|
||||
}
|
||||
|
||||
newSize := uint32(offset) + size
|
||||
s.nodes = s.nodes[:newSize]
|
||||
return uint32(offset), nil
|
||||
}
|
||||
|
||||
func (s *Skiplist) node(offset uint32) *node {
|
||||
return (*node)(unsafe.Pointer(&s.nodes[offset]))
|
||||
}
|
||||
|
||||
func (s *Skiplist) randomHeight() uint32 {
|
||||
rnd := uint32(s.rand.Uint64())
|
||||
h := uint32(1)
|
||||
for h < maxHeight && rnd <= probabilities[h] {
|
||||
h++
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
func (s *Skiplist) findSplice(key []byte, abbreviatedKey uint64, spl *[maxHeight]splice) {
|
||||
prev := s.head
|
||||
|
||||
for level := s.height - 1; ; level-- {
|
||||
// The code in this loop is the same as findSpliceForLevel(). For some
|
||||
// reason, calling findSpliceForLevel() here is much much slower than the
|
||||
// inlined code below. The excess time is also caught up in the final
|
||||
// return statement which makes little sense. Revisit when in go1.14 or
|
||||
// later if inlining improves.
|
||||
|
||||
next := s.getNext(prev, level)
|
||||
for next != s.tail {
|
||||
// Assume prev.key < key.
|
||||
nextNode := s.node(next)
|
||||
nextAbbreviatedKey := nextNode.abbreviatedKey
|
||||
if abbreviatedKey < nextAbbreviatedKey {
|
||||
// We are done for this level, since prev.key < key < next.key.
|
||||
break
|
||||
}
|
||||
if abbreviatedKey == nextAbbreviatedKey {
|
||||
if s.cmp(key, (*s.storage)[nextNode.keyStart:nextNode.keyEnd]) <= 0 {
|
||||
// We are done for this level, since prev.key < key <= next.key.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Keep moving right on this level.
|
||||
prev = next
|
||||
next = nextNode.links[level].next
|
||||
}
|
||||
|
||||
spl[level].prev = prev
|
||||
spl[level].next = next
|
||||
if level == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Skiplist) findSpliceForLevel(
|
||||
key []byte, abbreviatedKey uint64, level, start uint32,
|
||||
) (prev, next uint32) {
|
||||
prev = start
|
||||
next = s.getNext(prev, level)
|
||||
|
||||
for next != s.tail {
|
||||
// Assume prev.key < key.
|
||||
nextNode := s.node(next)
|
||||
nextAbbreviatedKey := nextNode.abbreviatedKey
|
||||
if abbreviatedKey < nextAbbreviatedKey {
|
||||
// We are done for this level, since prev.key < key < next.key.
|
||||
break
|
||||
}
|
||||
if abbreviatedKey == nextAbbreviatedKey {
|
||||
if s.cmp(key, (*s.storage)[nextNode.keyStart:nextNode.keyEnd]) <= 0 {
|
||||
// We are done for this level, since prev.key < key < next.key.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Keep moving right on this level.
|
||||
prev = next
|
||||
next = nextNode.links[level].next
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Skiplist) getKey(nd uint32) base.InternalKey {
|
||||
n := s.node(nd)
|
||||
kind := base.InternalKeyKind((*s.storage)[n.offset])
|
||||
key := (*s.storage)[n.keyStart:n.keyEnd]
|
||||
return base.MakeInternalKey(key, uint64(n.offset)|base.InternalKeySeqNumBatch, kind)
|
||||
}
|
||||
|
||||
func (s *Skiplist) getNext(nd, h uint32) uint32 {
|
||||
return s.node(nd).links[h].next
|
||||
}
|
||||
|
||||
func (s *Skiplist) getPrev(nd, h uint32) uint32 {
|
||||
return s.node(nd).links[h].prev
|
||||
}
|
||||
|
||||
func (s *Skiplist) debug() string {
|
||||
var buf bytes.Buffer
|
||||
for level := uint32(0); level < s.height; level++ {
|
||||
var count int
|
||||
for nd := s.head; nd != s.tail; nd = s.getNext(nd, level) {
|
||||
count++
|
||||
}
|
||||
fmt.Fprintf(&buf, "%d: %d\n", level, count)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Silence unused warning.
|
||||
var _ = (*Skiplist).debug
|
||||
Reference in New Issue
Block a user