From 50d815313efd6f573f64fd41d80aa18cb512c72f Mon Sep 17 00:00:00 2001 From: CPerezz Date: Sun, 19 Apr 2026 08:00:33 +0200 Subject: [PATCH] trie/bintrie: reuse path buffer in collectNodes Post-rollback pprof on BenchmarkCollectNodesSparseWrite revealed collectNodes' per-descent leftPath/rightPath make+copy as 13% of alloc_objects (~26 allocs/op). Replace with append/truncate on a shared buffer pre-sized by Commit; flushfn consumers (NodeSet.AddNode, tracer.Get) already clone via string(path), so in-place reuse is safe. Benchmark delta (M4 Pro, go1.24.0, --count=5 --benchtime=5s): before: 9506 ns/op 15245 B/op 132 allocs/op after: 9095 ns/op 15008 B/op 106 allocs/op vs upstream/master@53ff723cc: allocs/op now -20.9% (was -1.5%). --- trie/bintrie/store_commit.go | 16 ++++++++-------- trie/bintrie/trie.go | 5 ++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/trie/bintrie/store_commit.go b/trie/bintrie/store_commit.go index 9d73cc14d5..368428c7a3 100644 --- a/trie/bintrie/store_commit.go +++ b/trie/bintrie/store_commit.go @@ -240,18 +240,18 @@ func (s *NodeStore) collectNodes(ref nodeRef, path []byte, flushfn NodeFlushFn) if !node.dirty { return nil } - leftPath := make([]byte, len(path)+1) - copy(leftPath, path) - leftPath[len(path)] = 0 - if err := s.collectNodes(node.left, leftPath, flushfn); err != nil { + // Reuse path buffer across children: flushfn consumers + // (NodeSet.AddNode, tracer.Get) clone via string(path), so in-place + // mutation is safe. Saves ~17 allocs/op on this benchmark. + path = append(path, 0) + if err := s.collectNodes(node.left, path, flushfn); err != nil { return err } - rightPath := make([]byte, len(path)+1) - copy(rightPath, path) - rightPath[len(path)] = 1 - if err := s.collectNodes(node.right, rightPath, flushfn); err != nil { + path[len(path)-1] = 1 + if err := s.collectNodes(node.right, path, flushfn); err != nil { return err } + path = path[:len(path)-1] flushfn(path, s.computeHash(ref), s.serializeNode(ref)) node.dirty = false return nil diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index ced6d92eed..6be49e905c 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -309,7 +309,10 @@ func (t *BinaryTrie) Hash() common.Hash { func (t *BinaryTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) { nodeset := trienode.NewNodeSet(common.Hash{}) - err := t.store.collectNodes(t.store.root, nil, func(path []byte, hash common.Hash, serialized []byte) { + // Pre-size the path buffer: collectNodes reuses it in-place via + // append/truncate; 32 covers typical binary-trie depth without regrowth. + pathBuf := make([]byte, 0, 32) + err := t.store.collectNodes(t.store.root, pathBuf, func(path []byte, hash common.Hash, serialized []byte) { nodeset.AddNode(path, trienode.NewNodeWithPrev(hash, serialized, t.tracer.Get(path))) }) if err != nil {