Skip to content

Commit b782292

Browse files
committed
optimize iterate over loose N48 node
Signed-off-by: you06 <you1474600@gmail.com>
1 parent 63c2eff commit b782292

File tree

3 files changed

+84
-6
lines changed

3 files changed

+84
-6
lines changed

tree_benchmark_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,27 @@ func BenchmarkHSKTreeForEach(b *testing.B) {
189189
stats := collectStats(tree.Iterator(TraverseAll))
190190
assert.Equal(b, treeStats{4995, 1630, 276, 21, 4}, stats)
191191
}
192+
193+
func BenchmarkN48LooseScan(b *testing.B) {
194+
tree := New()
195+
196+
for i := 0; i < 17; i++ {
197+
for j := 0; j < 17; j++ {
198+
for k := 0; k < 17; k++ {
199+
for l := 0; l < 17; l++ {
200+
key := []byte{byte(i), byte(j), byte(k), byte(l)}
201+
tree.Insert(key, key)
202+
}
203+
}
204+
}
205+
}
206+
207+
subBench := func(b *testing.B, options ...int) {
208+
b.ResetTimer()
209+
stats := collectStats(tree.Iterator(options...))
210+
assert.Equal(b, treeStats{83521, 0, 0, 5220, 0}, stats)
211+
}
212+
213+
b.Run("Iterator", func(b *testing.B) { subBench(b, TraverseAll) })
214+
b.Run("IteratorReverse", func(b *testing.B) { subBench(b, TraverseAll, TraverseReverse) })
215+
}

tree_traversal.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package art
22

3+
import (
4+
"math"
5+
"math/bits"
6+
)
7+
38
// traverseAction is an action to be taken during tree traversal.
49
type traverseAction int
510

@@ -104,28 +109,40 @@ func (ctx *traverse48Context) ascTraversal() (int, bool) {
104109
return node48Max, true
105110
}
106111

107-
for ; ctx.curKeyIdx < node256Max; ctx.curKeyIdx++ {
108-
if ctx.n48.hasChild(ctx.curKeyIdx) {
112+
for curOffset := ctx.curKeyIdx >> n48bitShift; curOffset < 4; curOffset++ {
113+
offset := ctx.curKeyIdx % n48maskLen
114+
mask := math.MaxUint64 - (uint64(1) << offset) + 1 // e.g. offset=3 => 0b111...111000
115+
curr := ctx.n48.present[curOffset] & mask
116+
zeros := bits.TrailingZeros64(curr)
117+
if zeros < n48maskLen {
118+
ctx.curKeyIdx = curOffset*n48maskLen + zeros
109119
ctx.curKeyCh = ctx.n48.keys[ctx.curKeyIdx]
110120
ctx.curKeyIdx++
111-
112121
return int(ctx.curKeyCh), true
113122
}
123+
ctx.curKeyIdx = 0
114124
}
125+
ctx.curKeyIdx = node256Max
115126

116127
return 0, false
117128
}
118129

119130
// descTraversal traverses the children in descending order.
120131
func (ctx *traverse48Context) descTraversal() (int, bool) {
121-
for ; ctx.curKeyIdx > 0; ctx.curKeyIdx-- {
122-
if ctx.n48.hasChild(ctx.curKeyIdx) {
132+
for curOffset := ctx.curKeyIdx >> n48bitShift; curOffset >= 0; curOffset-- {
133+
offset := ctx.curKeyIdx % n48maskLen
134+
mask := uint64(1)<<(offset+1) - 1 // e.g. offset=3 => 0b000...0001111
135+
curr := ctx.n48.present[curOffset] & mask
136+
zeros := bits.LeadingZeros64(curr)
137+
if zeros < n48maskLen {
138+
ctx.curKeyIdx = curOffset*n48maskLen + n48maskLen - (zeros + 1)
123139
ctx.curKeyCh = ctx.n48.keys[ctx.curKeyIdx]
124140
ctx.curKeyIdx--
125-
126141
return int(ctx.curKeyCh), true
127142
}
143+
ctx.curKeyIdx = n48maskLen - 1
128144
}
145+
ctx.curKeyIdx = -1
129146

130147
if !ctx.zeroChildDone {
131148
ctx.zeroChildDone = true

tree_traversal_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,3 +589,40 @@ func TestIteratorEmptyTreeBehavior(t *testing.T) {
589589
assert.Nil(t, n)
590590
assert.Equal(t, ErrNoMoreNodes, err)
591591
}
592+
593+
func TestIteratorStatsWithReverse(t *testing.T) {
594+
t.Parallel()
595+
596+
testCases := map[int]Kind{
597+
node4Max + 1: Node16,
598+
node16Max + 1: Node48,
599+
node48Max + 1: Node256,
600+
}
601+
for leafCount, expectedKind := range testCases {
602+
tree := New()
603+
for i := 0; i < leafCount; i++ {
604+
key := []byte{byte(i)}
605+
tree.Insert(key, key)
606+
}
607+
608+
expectedStats := treeStats{
609+
leafCount: leafCount,
610+
}
611+
switch expectedKind {
612+
case Node16:
613+
expectedStats.node16Count = 1
614+
case Node48:
615+
expectedStats.node48Count = 1
616+
case Node256:
617+
expectedStats.node256Count = 1
618+
default:
619+
t.Fatalf("unexpected expectedKind: %v", expectedKind)
620+
}
621+
622+
stats := collectStats(tree.Iterator(TraverseAll))
623+
assert.Equal(t, expectedStats, stats)
624+
625+
stats = collectStats(tree.Iterator(TraverseAll, TraverseReverse))
626+
assert.Equal(t, expectedStats, stats)
627+
}
628+
}

0 commit comments

Comments
 (0)