From ef3217c249bb522611290d61af7d5720430412d8 Mon Sep 17 00:00:00 2001 From: CPerezz Date: Sun, 19 Apr 2026 08:06:18 +0200 Subject: [PATCH] trie/bintrie: keep StemNode.Hash's data array on stack MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pooled hash.Hash interface forced the local [StemNodeWidth]common.Hash data array to escape to the heap: h.Sum(data[i][:0]) passes a subslice of data into an interface method, so escape analysis conservatively moves the whole array. pprof (post-rollback) showed this single allocation as 52% of total bytes (5 GB over BenchmarkCollectNodesSparseWrite). Switch to sha256.Sum256 (takes []byte, returns [32]byte by value) — no slice into data ever leaves the frame, so data stays on stack. Also drops per-Hash h.Sum(nil) allocs and the sync.Pool Get/Put round-trip for stems. Benchmark delta (M4 Pro, go1.24.0, --count=5 --benchtime=5s): before: 9095 ns/op 15008 B/op 106 allocs/op after: 9133 ns/op 6526 B/op 95 allocs/op vs upstream/master@53ff723cc: bytes/op -82.7% (was -60%), allocs/op -29.1% (was -20.9%). --- trie/bintrie/stem_node.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go index 9944fc92f1..6d6b261f37 100644 --- a/trie/bintrie/stem_node.go +++ b/trie/bintrie/stem_node.go @@ -17,6 +17,8 @@ package bintrie import ( + "crypto/sha256" + "github.com/ethereum/go-ethereum/common" ) @@ -58,36 +60,36 @@ func (sn *StemNode) Hash() common.Hash { return sn.hash } + // Use sha256.Sum256 (returns [32]byte by value) instead of a pooled + // hash.Hash: feeding data[i][:0] into the interface method Sum forces + // data to heap (escape analysis is conservative through interfaces). + // Sum256 takes []byte and returns by value, so data stays on stack. var data [StemNodeWidth]common.Hash - h := newSha256() - defer returnSha256(h) for i, v := range sn.values { if v != nil { - h.Reset() - h.Write(v) - h.Sum(data[i][:0]) + data[i] = sha256.Sum256(v) } } + var pair [2 * HashSize]byte for level := 1; level <= 8; level++ { for i := range StemNodeWidth / (1 << level) { if data[i*2] == (common.Hash{}) && data[i*2+1] == (common.Hash{}) { data[i] = common.Hash{} continue } - h.Reset() - h.Write(data[i*2][:]) - h.Write(data[i*2+1][:]) - data[i] = common.Hash(h.Sum(nil)) + copy(pair[:HashSize], data[i*2][:]) + copy(pair[HashSize:], data[i*2+1][:]) + data[i] = sha256.Sum256(pair[:]) } } - h.Reset() - h.Write(sn.Stem[:]) - h.Write([]byte{0}) - h.Write(data[0][:]) - sn.hash = common.BytesToHash(h.Sum(nil)) + var final [StemSize + 1 + HashSize]byte + copy(final[:StemSize], sn.Stem[:]) + final[StemSize] = 0 + copy(final[StemSize+1:], data[0][:]) + sn.hash = sha256.Sum256(final[:]) sn.mustRecompute = false return sn.hash }