Skip to content

Commit

Permalink
perf: use a reusable bytes.Buffer to speed up MutableTree.String (#456)
Browse files Browse the repository at this point in the history
The prior code used a naive string concatenation
str += "..."

which is very slow and inefficient especially when iterating over
all the keys, but also it performed an unnecessary byteslice->string
conversion for all arguments inside (nodeDB).traverse* using

    str += fmt.Sprintf("%s: %x\n", string(key), value)

notice the `string(key)` passed into fmt.Sprintf("%s"?
At bare minimum that code should just simply be:

    str += fmt.Sprintf("%s: %x\n", key, value)

per https://twitter.com/orijtech/status/1462100381803102213?s=20
which saves a whole lot of cycles and RAM.

This change uses:
* (*bytes.Buffer) in combination with fmt.Fprintf
* A sync.Pool with (*bytes.Buffer) values to reuse buffers

and the results are profound:

```shell
$ benchstat before.txt after.txt
name          old time/op    new time/op    delta
TreeString-8     111ms ± 8%       4ms ± 4%  -96.01%  (p=0.000 n=20+20)

name          old alloc/op   new alloc/op   delta
TreeString-8     734MB ± 0%       2MB ± 1%  -99.72%  (p=0.000 n=20+20)

name          old allocs/op  new allocs/op  delta
TreeString-8     37.0k ± 0%     28.7k ± 0%  -22.40%  (p=0.000 n=20+20)
```

Fixes #455
  • Loading branch information
odeke-em authored Dec 16, 2021
1 parent 0568d39 commit a6b922c
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 10 deletions.
23 changes: 13 additions & 10 deletions nodedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,33 +698,36 @@ func (ndb *nodeDB) traverseNodes(fn func(hash []byte, node *Node)) {
}

func (ndb *nodeDB) String() string {
var str string
buf := bufPool.Get().(*bytes.Buffer)
defer bufPool.Put(buf)
buf.Reset()

index := 0

ndb.traversePrefix(rootKeyFormat.Key(), func(key, value []byte) {
str += fmt.Sprintf("%s: %x\n", string(key), value)
fmt.Fprintf(buf, "%s: %x\n", key, value)
})
str += "\n"
buf.WriteByte('\n')

ndb.traverseOrphans(func(key, value []byte) {
str += fmt.Sprintf("%s: %x\n", string(key), value)
fmt.Fprintf(buf, "%s: %x\n", key, value)
})
str += "\n"
buf.WriteByte('\n')

ndb.traverseNodes(func(hash []byte, node *Node) {
switch {
case len(hash) == 0:
str += "<nil>\n"
buf.WriteByte('\n')
case node == nil:
str += fmt.Sprintf("%s%40x: <nil>\n", nodeKeyFormat.Prefix(), hash)
fmt.Fprintf(buf, "%s%40x: <nil>\n", nodeKeyFormat.Prefix(), hash)
case node.value == nil && node.height > 0:
str += fmt.Sprintf("%s%40x: %s %-16s h=%d version=%d\n",
fmt.Fprintf(buf, "%s%40x: %s %-16s h=%d version=%d\n",
nodeKeyFormat.Prefix(), hash, node.key, "", node.height, node.version)
default:
str += fmt.Sprintf("%s%40x: %s = %-16s h=%d version=%d\n",
fmt.Fprintf(buf, "%s%40x: %s = %-16s h=%d version=%d\n",
nodeKeyFormat.Prefix(), hash, node.key, node.value, node.height, node.version)
}
index++
})
return "-" + "\n" + str + "-"
return "-" + "\n" + buf.String() + "-"
}
35 changes: 35 additions & 0 deletions nodedb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import (
"encoding/binary"
"math/rand"
"testing"

"github.com/stretchr/testify/require"

dbm "github.com/tendermint/tm-db"
)

func BenchmarkNodeKey(b *testing.B) {
Expand All @@ -22,6 +26,37 @@ func BenchmarkOrphanKey(b *testing.B) {
}
}

func BenchmarkTreeString(b *testing.B) {
tree := makeAndPopulateMutableTree(b)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sink = tree.String()
}

if sink == nil {
b.Fatal("Benchmark did not run")
}
sink = (interface{})(nil)
}

func makeAndPopulateMutableTree(tb testing.TB) *MutableTree {
memDB := dbm.NewMemDB()
tree, err := NewMutableTreeWithOpts(memDB, 0, &Options{InitialVersion: 9})
require.NoError(tb, err)

for i := 0; i < 1e4; i++ {
buf := make([]byte, 0, (i/255)+1)
for j := 0; 1<<j <= i; j++ {
buf = append(buf, byte((i>>j)&0xff))
}
tree.Set(buf, buf)
}
_, _, err = tree.SaveVersion()
require.Nil(tb, err, "Expected .SaveVersion to succeed")
return tree
}

func makeHashes(b *testing.B, seed int64) [][]byte {
b.StopTimer()
rnd := rand.NewSource(seed)
Expand Down
1 change: 1 addition & 0 deletions with_gcc_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build gcc
// +build gcc

// This file exists because some of the DBs e.g CLevelDB
Expand Down

0 comments on commit a6b922c

Please sign in to comment.