Skip to content

Commit

Permalink
storage/cmdq: remove parent pointers and position fields
Browse files Browse the repository at this point in the history
This commit rips out `node.parent` and `node.pos`. These won't work
with a copy-on-write strategy. This makes the iterator code a little
more complicated because the iterator now needs to maintain a stack
of nodes and positions.

We avoid any allocations for this stack for trees up to height 4 (~130k
items) Because of this, the change does not seem to have any effect on
the benchmarks. Theoretically, it should actually speed up mutations
to the tree.

Release note: None
  • Loading branch information
nvanbenschoten committed Nov 13, 2018
1 parent ebec027 commit 4a02b8c
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 82 deletions.
136 changes: 68 additions & 68 deletions pkg/storage/cmdq/interval_btree.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,10 @@ func upperBound(c *cmd) keyBound {
}

type leafNode struct {
parent *node
max keyBound
pos int16
count int16
leaf bool
cmds [maxCmds]*cmd
max keyBound
count int16
leaf bool
cmds [maxCmds]*cmd
}

func newLeafNode() *node {
Expand All @@ -123,25 +121,16 @@ type node struct {
children [maxCmds + 1]*node
}

func (n *node) updatePos(start, end int) {
for i := start; i < end; i++ {
n.children[i].pos = int16(i)
}
}

func (n *node) insertAt(index int, c *cmd, nd *node) {
if index < int(n.count) {
copy(n.cmds[index+1:n.count+1], n.cmds[index:n.count])
if !n.leaf {
copy(n.children[index+2:n.count+2], n.children[index+1:n.count+1])
n.updatePos(index+2, int(n.count+2))
}
}
n.cmds[index] = c
if !n.leaf {
n.children[index+1] = nd
nd.parent = n
nd.pos = int16(index + 1)
}
n.count++
}
Expand All @@ -150,19 +139,14 @@ func (n *node) pushBack(c *cmd, nd *node) {
n.cmds[n.count] = c
if !n.leaf {
n.children[n.count+1] = nd
nd.parent = n
nd.pos = n.count + 1
}
n.count++
}

func (n *node) pushFront(c *cmd, nd *node) {
if !n.leaf {
copy(n.children[1:n.count+2], n.children[:n.count+1])
n.updatePos(1, int(n.count+2))
n.children[0] = nd
nd.parent = n
nd.pos = 0
}
copy(n.cmds[1:n.count+1], n.cmds[:n.count])
n.cmds[0] = c
Expand All @@ -176,7 +160,6 @@ func (n *node) removeAt(index int) (*cmd, *node) {
if !n.leaf {
child = n.children[index+1]
copy(n.children[index+1:n.count], n.children[index+2:n.count+1])
n.updatePos(index+1, int(n.count))
n.children[n.count] = nil
}
n.count--
Expand Down Expand Up @@ -206,7 +189,6 @@ func (n *node) popFront() (*cmd, *node) {
if !n.leaf {
child = n.children[0]
copy(n.children[:n.count+1], n.children[1:n.count+2])
n.updatePos(0, int(n.count+1))
n.children[n.count+1] = nil
}
out := n.cmds[0]
Expand Down Expand Up @@ -276,10 +258,6 @@ func (n *node) split(i int) (*cmd, *node) {
for j := int16(i + 1); j <= n.count; j++ {
n.children[j] = nil
}
for j := int16(0); j <= next.count; j++ {
next.children[j].parent = next
next.children[j].pos = j
}
}
n.count = int16(i)

Expand Down Expand Up @@ -492,10 +470,6 @@ func (n *node) rebalanceOrMerge(i int) {
copy(child.cmds[child.count+1:], mergeChild.cmds[:mergeChild.count])
if !child.leaf {
copy(child.children[child.count+1:], mergeChild.children[:mergeChild.count+1])
for i := int16(0); i <= mergeChild.count; i++ {
mergeChild.children[i].parent = child
}
child.updatePos(int(child.count+1), int(child.count+mergeChild.count+2))
}
child.count += mergeChild.count + 1

Expand Down Expand Up @@ -592,7 +566,6 @@ func (t *btree) Delete(c *cmd) {
}
if t.root.count == 0 && !t.root.leaf {
t.root = t.root.children[0]
t.root.parent = nil
}
}

Expand All @@ -609,10 +582,6 @@ func (t *btree) Set(c *cmd) {
newRoot.children[0] = t.root
newRoot.children[1] = splitNode
newRoot.max = newRoot.findUpperBound()
t.root.parent = newRoot
t.root.pos = 0
splitNode.parent = newRoot
splitNode.pos = 1
t.root = newRoot
}
if replaced, _ := t.root.insert(c); !replaced {
Expand Down Expand Up @@ -678,19 +647,53 @@ func (n *node) writeString(b *strings.Builder) {

// iterator is responsible for search and traversal within a btree.
type iterator struct {
t *btree
t *btree
n *node
pos int16
s []iterFrame
sBuf [3]iterFrame // avoids allocation of s up to height 4
o overlapScan
}

type iterFrame struct {
n *node
pos int16
o overlapScan
}

func (i *iterator) reset() {
i.n = i.t.root
i.s = i.s[:0]
i.pos = -1
i.o = overlapScan{}
}

// descend descends into the node's child at position pos. It maintains a
// stack of (parent, position) pairs so that the tree can be ascended again
// later.
func (i *iterator) descend(n *node, pos int16) {
if i.s == nil {
i.s = i.sBuf[:0]
}
i.s = append(i.s, iterFrame{n: n, pos: pos})
i.n = n.children[pos]
i.pos = 0
}

// ascend ascends up to the current node's parent and resets the position
// to the one previously set for this parent node.
func (i *iterator) ascend() {
f := i.s[len(i.s)-1]
i.s = i.s[:len(i.s)-1]
i.n = f.n
i.pos = f.pos
}

// SeekGE seeks to the first cmd greater-than or equal to the provided cmd.
func (i *iterator) SeekGE(c *cmd) {
i.n = i.t.root
i.reset()
if i.n == nil {
return
}
i.o = overlapScan{}
for {
pos, found := i.n.find(c)
i.pos = int16(pos)
Expand All @@ -703,51 +706,48 @@ func (i *iterator) SeekGE(c *cmd) {
}
return
}
i.n = i.n.children[i.pos]
i.descend(i.n, i.pos)
}
}

// SeekLT seeks to the first cmd less-than the provided cmd.
func (i *iterator) SeekLT(c *cmd) {
i.n = i.t.root
i.reset()
if i.n == nil {
return
}
i.o = overlapScan{}
for {
pos, found := i.n.find(c)
i.pos = int16(pos)
if found || i.n.leaf {
i.Prev()
return
}
i.n = i.n.children[i.pos]
i.descend(i.n, i.pos)
}
}

// First seeks to the first cmd in the btree.
func (i *iterator) First() {
i.n = i.t.root
i.reset()
if i.n == nil {
return
}
for !i.n.leaf {
i.n = i.n.children[0]
i.descend(i.n, 0)
}
i.o = overlapScan{}
i.pos = 0
}

// Last seeks to the last cmd in the btree.
func (i *iterator) Last() {
i.n = i.t.root
i.reset()
if i.n == nil {
return
}
for !i.n.leaf {
i.n = i.n.children[i.n.count]
i.descend(i.n, i.n.count)
}
i.o = overlapScan{}
i.pos = i.n.count - 1
}

Expand All @@ -763,16 +763,15 @@ func (i *iterator) Next() {
if i.pos < i.n.count {
return
}
for i.n.parent != nil && i.pos >= i.n.count {
i.pos = i.n.pos
i.n = i.n.parent
for len(i.s) > 0 && i.pos >= i.n.count {
i.ascend()
}
return
}

i.n = i.n.children[i.pos+1]
i.descend(i.n, i.pos+1)
for !i.n.leaf {
i.n = i.n.children[0]
i.descend(i.n, 0)
}
i.pos = 0
}
Expand All @@ -789,16 +788,16 @@ func (i *iterator) Prev() {
if i.pos >= 0 {
return
}
for i.n.parent != nil && i.pos < 0 {
i.pos = i.n.pos - 1
i.n = i.n.parent
for len(i.s) > 0 && i.pos < 0 {
i.ascend()
i.pos--
}
return
}

i.n = i.n.children[i.pos]
i.descend(i.n, i.pos)
for !i.n.leaf {
i.n = i.n.children[i.n.count]
i.descend(i.n, i.n.count)
}
i.pos = i.n.count - 1
}
Expand Down Expand Up @@ -874,7 +873,7 @@ type overlapScan struct {
// FirstOverlap seeks to the first cmd in the btree that overlaps with the
// provided search cmd.
func (i *iterator) FirstOverlap(c *cmd) {
i.n = i.t.root
i.reset()
if i.n == nil {
return
}
Expand All @@ -901,8 +900,9 @@ func (i *iterator) NextOverlap() {
}

func (i *iterator) constrainMinSearchBounds() {
k := i.o.c.span.Key
j := sort.Search(int(i.n.count), func(j int) bool {
return bytes.Compare(i.o.c.span.Key, i.n.cmds[j].span.Key) <= 0
return bytes.Compare(k, i.n.cmds[j].span.Key) <= 0
})
i.o.constrMinN = i.n
i.o.constrMinPos = int16(j)
Expand All @@ -921,23 +921,23 @@ func (i *iterator) findNextOverlap() {
for {
if i.pos > i.n.count {
// Iterate up tree.
if i.n.parent == nil {
if len(i.s) == 0 {
// Should have already hit upper-bound constraint.
panic("unreachable")
}
i.pos = i.n.pos
i.n = i.n.parent
i.ascend()
} else if !i.n.leaf {
// Iterate down tree.
if i.o.constrMinReached || i.n.children[i.pos].max.contains(i.o.c) {
i.n = i.n.children[i.pos]
i.pos = 0
par := i.n
pos := i.pos
i.descend(par, pos)

// Refine the constraint bounds, if necessary.
if i.n.parent == i.o.constrMinN && i.n.pos == i.o.constrMinPos {
if par == i.o.constrMinN && pos == i.o.constrMinPos {
i.constrainMinSearchBounds()
}
if i.n.parent == i.o.constrMaxN && i.n.pos == i.o.constrMaxPos {
if par == i.o.constrMaxN && pos == i.o.constrMaxPos {
i.constrainMaxSearchBounds()
}
continue
Expand Down
15 changes: 1 addition & 14 deletions pkg/storage/cmdq/interval_btree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ func (t *btree) Verify(tt *testing.T) {
return
}
t.verifyLeafSameDepth(tt)
t.verifyParentAndPosSet(tt)
t.verifyCountAllowed(tt)
t.isSorted(tt)
t.isUpperBoundCorrect(tt)
Expand All @@ -55,18 +54,6 @@ func (n *node) verifyDepthEqualToHeight(t *testing.T, depth, height int) {
})
}

func (t *btree) verifyParentAndPosSet(tt *testing.T) {
t.root.verifyParentAndPosSet(tt, nil, 0)
}

func (n *node) verifyParentAndPosSet(t *testing.T, par *node, pos int16) {
require.Equal(t, par, n.parent)
require.Equal(t, pos, n.pos)
n.recurse(func(child *node, pos int16) {
child.verifyParentAndPosSet(t, n, pos)
})
}

func (t *btree) verifyCountAllowed(tt *testing.T) {
t.root.verifyCountAllowed(tt, true)
}
Expand Down Expand Up @@ -217,7 +204,7 @@ func checkIter(t *testing.T, it iterator, start, end int) {
}
}
if i != start {
t.Fatalf("expected %d, but at %d: %+v parent=%p", start, i, it, it.n.parent)
t.Fatalf("expected %d, but at %d: %+v", start, i, it)
}

all := newCmd(spanWithEnd(start, end))
Expand Down

0 comments on commit 4a02b8c

Please sign in to comment.