Skip to content

Commit d9460e3

Browse files
authored
feat(trie): faster header decoding (#2649)
- Performance increases as number of variants increases - Changed from slice to fixed size array (thanks Eclesio) - Update comments - Add test verifying slice is sorted by bit mask - Update relevant benchmark for 7 variants
1 parent cb1da40 commit d9460e3

File tree

2 files changed

+37
-19
lines changed

2 files changed

+37
-19
lines changed

internal/trie/node/header.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,28 +123,29 @@ func decodeHeader(reader io.Reader) (variant byte,
123123

124124
var ErrVariantUnknown = errors.New("node variant is unknown")
125125

126+
// variantsOrderedByBitMask is an array of all variants sorted
127+
// in ascending order by the number of LHS set bits each variant mask has.
128+
// See https://spec.polkadot.network/#defn-node-header
129+
// WARNING: DO NOT MUTATE.
130+
// This array is defined at global scope for performance
131+
// reasons only, instead of having it locally defined in
132+
// the decodeHeaderByte function below.
133+
// For 7 variants, the performance is improved by ~20%.
134+
var variantsOrderedByBitMask = [...]variant{
135+
leafVariant, // mask 1100_0000
136+
branchVariant, // mask 1100_0000
137+
branchWithValueVariant, // mask 1100_0000
138+
}
139+
126140
func decodeHeaderByte(header byte) (variantBits,
127141
partialKeyLengthHeader, partialKeyLengthHeaderMask byte, err error) {
128-
// variants is a slice of all variants sorted in ascending
129-
// order by the number of bits each variant mask occupy
130-
// in the header byte.
131-
// See https://spec.polkadot.network/#defn-node-header
132-
// Performance note: see `Benchmark_decodeHeaderByte`;
133-
// running with a locally scoped slice is as fast as having
134-
// it at global scope.
135-
variants := []variant{
136-
leafVariant, // mask 1100_0000
137-
branchVariant, // mask 1100_0000
138-
branchWithValueVariant, // mask 1100_0000
139-
}
140-
141-
for i := len(variants) - 1; i >= 0; i-- {
142-
variantBits = header & variants[i].mask
143-
if variantBits != variants[i].bits {
142+
for i := len(variantsOrderedByBitMask) - 1; i >= 0; i-- {
143+
variantBits = header & variantsOrderedByBitMask[i].mask
144+
if variantBits != variantsOrderedByBitMask[i].bits {
144145
continue
145146
}
146147

147-
partialKeyLengthHeaderMask = ^variants[i].mask
148+
partialKeyLengthHeaderMask = ^variantsOrderedByBitMask[i].mask
148149
partialKeyLengthHeader = header & partialKeyLengthHeaderMask
149150
return variantBits, partialKeyLengthHeader,
150151
partialKeyLengthHeaderMask, nil

internal/trie/node/header_test.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"io"
99
"math"
10+
"sort"
1011
"testing"
1112

1213
"github.com/golang/mock/gomock"
@@ -419,11 +420,27 @@ func Test_decodeHeaderByte(t *testing.T) {
419420
}
420421
}
421422

423+
func Test_variantsOrderedByBitMask(t *testing.T) {
424+
t.Parallel()
425+
426+
slice := make([]variant, len(variantsOrderedByBitMask))
427+
sortedSlice := make([]variant, len(variantsOrderedByBitMask))
428+
copy(slice, variantsOrderedByBitMask[:])
429+
copy(sortedSlice, variantsOrderedByBitMask[:])
430+
431+
sort.Slice(slice, func(i, j int) bool {
432+
return slice[i].mask > slice[j].mask
433+
})
434+
435+
assert.Equal(t, sortedSlice, slice)
436+
}
437+
422438
func Benchmark_decodeHeaderByte(b *testing.B) {
439+
// For 7 variants defined in the variants array:
423440
// With global scoped variants slice:
424-
// 3.453 ns/op 0 B/op 0 allocs/op
441+
// 2.987 ns/op 0 B/op 0 allocs/op
425442
// With locally scoped variants slice:
426-
// 3.441 ns/op 0 B/op 0 allocs/op
443+
// 3.873 ns/op 0 B/op 0 allocs/op
427444
header := leafVariant.bits | 0b0000_0001
428445
b.ResetTimer()
429446
for i := 0; i < b.N; i++ {

0 commit comments

Comments
 (0)