core/types, trie: reduce allocations in derivesha (#30747)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

Alternative to #30746, potential follow-up to #30743 . This PR makes the
stacktrie always copy incoming value buffers, and reuse them internally.

Improvement in #30743:
```
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/types
cpu: 12th Gen Intel(R) Core(TM) i7-1270P
                          │ derivesha.1 │             derivesha.2              │
                          │   sec/op    │    sec/op     vs base                │
DeriveSha200/stack_trie-8   477.8µ ± 2%   430.0µ ± 12%  -10.00% (p=0.000 n=10)

                          │ derivesha.1  │             derivesha.2              │
                          │     B/op     │     B/op      vs base                │
DeriveSha200/stack_trie-8   45.17Ki ± 0%   25.65Ki ± 0%  -43.21% (p=0.000 n=10)

                          │ derivesha.1 │            derivesha.2             │
                          │  allocs/op  │ allocs/op   vs base                │
DeriveSha200/stack_trie-8   1259.0 ± 0%   232.0 ± 0%  -81.57% (p=0.000 n=10)

```
This PR further enhances that: 

```
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/types
cpu: 12th Gen Intel(R) Core(TM) i7-1270P
                          │ derivesha.2  │          derivesha.3           │
                          │    sec/op    │    sec/op     vs base          │
DeriveSha200/stack_trie-8   430.0µ ± 12%   423.6µ ± 13%  ~ (p=0.739 n=10)

                          │  derivesha.2  │             derivesha.3              │
                          │     B/op      │     B/op      vs base                │
DeriveSha200/stack_trie-8   25.654Ki ± 0%   4.960Ki ± 0%  -80.67% (p=0.000 n=10)

                          │ derivesha.2 │            derivesha.3             │
                          │  allocs/op  │ allocs/op   vs base                │
DeriveSha200/stack_trie-8   232.00 ± 0%   37.00 ± 0%  -84.05% (p=0.000 n=10)
```
So the total derivesha-improvement over *both PRS* is: 
```
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/core/types
cpu: 12th Gen Intel(R) Core(TM) i7-1270P
                          │ derivesha.1 │             derivesha.3              │
                          │   sec/op    │    sec/op     vs base                │
DeriveSha200/stack_trie-8   477.8µ ± 2%   423.6µ ± 13%  -11.33% (p=0.015 n=10)

                          │  derivesha.1  │             derivesha.3              │
                          │     B/op      │     B/op      vs base                │
DeriveSha200/stack_trie-8   45.171Ki ± 0%   4.960Ki ± 0%  -89.02% (p=0.000 n=10)

                          │ derivesha.1  │            derivesha.3             │
                          │  allocs/op   │ allocs/op   vs base                │
DeriveSha200/stack_trie-8   1259.00 ± 0%   37.00 ± 0%  -97.06% (p=0.000 n=10)
```

Since this PR always copies the incoming value, it adds a little bit of
a penalty on the previous insert-benchmark, which copied nothing (always
passed the same empty slice as input) :

```
goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/trie
cpu: 12th Gen Intel(R) Core(TM) i7-1270P
             │ stacktrie.7  │          stacktrie.10          │
             │    sec/op    │    sec/op     vs base          │
Insert100K-8   88.21m ± 34%   92.37m ± 31%  ~ (p=0.280 n=10)

             │ stacktrie.7  │             stacktrie.10             │
             │     B/op     │     B/op      vs base                │
Insert100K-8   3.424Ki ± 3%   4.581Ki ± 3%  +33.80% (p=0.000 n=10)

             │ stacktrie.7 │            stacktrie.10            │
             │  allocs/op  │ allocs/op   vs base                │
Insert100K-8    22.00 ± 5%   26.00 ± 4%  +18.18% (p=0.000 n=10)
```

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
Martin HS 2025-10-01 10:05:49 +02:00 committed by GitHub
parent 1487a8577d
commit 057667151b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 189 additions and 65 deletions

View file

@ -240,7 +240,7 @@ type extblock struct {
// //
// The receipt's bloom must already calculated for the block's bloom to be // The receipt's bloom must already calculated for the block's bloom to be
// correctly calculated. // correctly calculated.
func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher TrieHasher) *Block { func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher) *Block {
if body == nil { if body == nil {
body = &Body{} body = &Body{}
} }

View file

@ -27,7 +27,7 @@ import (
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
// hasherPool holds LegacyKeccak256 hashers for rlpHash. // hasherPool holds LegacyKeccak256 buffer for rlpHash.
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { return crypto.NewKeccakState() }, New: func() interface{} { return crypto.NewKeccakState() },
} }
@ -75,11 +75,17 @@ func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) {
return h return h
} }
// TrieHasher is the tool used to calculate the hash of derivable list. // ListHasher defines the interface for computing the hash of a derivable list.
// This is internal, do not use. type ListHasher interface {
type TrieHasher interface { // Reset clears the internal state of the hasher, preparing it for reuse.
Reset() Reset()
Update([]byte, []byte) error
// Update inserts the given key-value pair into the hasher.
// The implementation must copy the provided slices, allowing the caller
// to safely modify them after the call returns.
Update(key []byte, value []byte) error
// Hash computes and returns the final hash of all inserted key-value pairs.
Hash() common.Hash Hash() common.Hash
} }
@ -91,19 +97,20 @@ type DerivableList interface {
EncodeIndex(int, *bytes.Buffer) EncodeIndex(int, *bytes.Buffer)
} }
// encodeForDerive encodes the element in the list at the position i into the buffer.
func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte {
buf.Reset() buf.Reset()
list.EncodeIndex(i, buf) list.EncodeIndex(i, buf)
// It's really unfortunate that we need to perform this copy. return buf.Bytes()
// StackTrie holds onto the values until Hash is called, so the values
// written to it must not alias.
return common.CopyBytes(buf.Bytes())
} }
// DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header. // DeriveSha creates the tree hashes of transactions, receipts, and withdrawals in a block header.
func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { func DeriveSha(list DerivableList, hasher ListHasher) common.Hash {
hasher.Reset() hasher.Reset()
// Allocate a buffer for value encoding. As the hasher is claimed that all
// supplied key value pairs will be copied by hasher and safe to reuse the
// encoding buffer.
valueBuf := encodeBufferPool.Get().(*bytes.Buffer) valueBuf := encodeBufferPool.Get().(*bytes.Buffer)
defer encodeBufferPool.Put(valueBuf) defer encodeBufferPool.Put(valueBuf)

View file

@ -26,12 +26,10 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb"
) )
func TestDeriveSha(t *testing.T) { func TestDeriveSha(t *testing.T) {
@ -40,7 +38,7 @@ func TestDeriveSha(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
for len(txs) < 1000 { for len(txs) < 1000 {
exp := types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) exp := types.DeriveSha(txs, trie.NewListHasher())
got := types.DeriveSha(txs, trie.NewStackTrie(nil)) got := types.DeriveSha(txs, trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) { if !bytes.Equal(got[:], exp[:]) {
t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp)
@ -76,30 +74,45 @@ func TestEIP2718DeriveSha(t *testing.T) {
} }
} }
// goos: darwin
// goarch: arm64
// pkg: github.com/ethereum/go-ethereum/core/types
// cpu: Apple M1 Pro
// BenchmarkDeriveSha200
// BenchmarkDeriveSha200/std_trie
// BenchmarkDeriveSha200/std_trie-8 6754 174074 ns/op 80054 B/op 1926 allocs/op
// BenchmarkDeriveSha200/stack_trie
// BenchmarkDeriveSha200/stack_trie-8 7296 162675 ns/op 745 B/op 19 allocs/op
func BenchmarkDeriveSha200(b *testing.B) { func BenchmarkDeriveSha200(b *testing.B) {
txs, err := genTxs(200) txs, err := genTxs(200)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
var exp common.Hash want := types.DeriveSha(txs, trie.NewListHasher())
var got common.Hash
b.Run("std_trie", func(b *testing.B) { b.Run("std_trie", func(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
var have common.Hash
for b.Loop() { for b.Loop() {
exp = types.DeriveSha(txs, trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) have = types.DeriveSha(txs, trie.NewListHasher())
}
if have != want {
b.Errorf("have %x want %x", have, want)
} }
}) })
st := trie.NewStackTrie(nil)
b.Run("stack_trie", func(b *testing.B) { b.Run("stack_trie", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
var have common.Hash
for b.Loop() { for b.Loop() {
got = types.DeriveSha(txs, trie.NewStackTrie(nil)) st.Reset()
have = types.DeriveSha(txs, st)
}
if have != want {
b.Errorf("have %x want %x", have, want)
} }
}) })
if got != exp {
b.Errorf("got %x exp %x", got, exp)
}
} }
func TestFuzzDeriveSha(t *testing.T) { func TestFuzzDeriveSha(t *testing.T) {
@ -107,7 +120,7 @@ func TestFuzzDeriveSha(t *testing.T) {
rndSeed := mrand.Int() rndSeed := mrand.Int()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
seed := rndSeed + i seed := rndSeed + i
exp := types.DeriveSha(newDummy(i), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) exp := types.DeriveSha(newDummy(i), trie.NewListHasher())
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) { if !bytes.Equal(got[:], exp[:]) {
printList(t, newDummy(seed)) printList(t, newDummy(seed))
@ -135,7 +148,7 @@ func TestDerivableList(t *testing.T) {
}, },
} }
for i, tc := range tcs[1:] { for i, tc := range tcs[1:] {
exp := types.DeriveSha(flatList(tc), trie.NewEmpty(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil))) exp := types.DeriveSha(flatList(tc), trie.NewListHasher())
got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil))
if !bytes.Equal(got[:], exp[:]) { if !bytes.Equal(got[:], exp[:]) {
t.Fatalf("case %d: got %x exp %x", i, got, exp) t.Fatalf("case %d: got %x exp %x", i, got, exp)

View file

@ -23,6 +23,7 @@
package blocktest package blocktest
import ( import (
"bytes"
"hash" "hash"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -48,8 +49,8 @@ func (h *testHasher) Reset() {
// Update updates the hash state with the given key and value. // Update updates the hash state with the given key and value.
func (h *testHasher) Update(key, val []byte) error { func (h *testHasher) Update(key, val []byte) error {
h.hasher.Write(key) h.hasher.Write(bytes.Clone(key))
h.hasher.Write(val) h.hasher.Write(bytes.Clone(val))
return nil return nil
} }

View file

@ -32,8 +32,8 @@ func newBytesPool(sliceCap, nitems int) *bytesPool {
} }
} }
// Get returns a slice. Safe for concurrent use. // get returns a slice. Safe for concurrent use.
func (bp *bytesPool) Get() []byte { func (bp *bytesPool) get() []byte {
select { select {
case b := <-bp.c: case b := <-bp.c:
return b return b
@ -42,18 +42,18 @@ func (bp *bytesPool) Get() []byte {
} }
} }
// GetWithSize returns a slice with specified byte slice size. // getWithSize returns a slice with specified byte slice size.
func (bp *bytesPool) GetWithSize(s int) []byte { func (bp *bytesPool) getWithSize(s int) []byte {
b := bp.Get() b := bp.get()
if cap(b) < s { if cap(b) < s {
return make([]byte, s) return make([]byte, s)
} }
return b[:s] return b[:s]
} }
// Put returns a slice to the pool. Safe for concurrent use. This method // put returns a slice to the pool. Safe for concurrent use. This method
// will ignore slices that are too small or too large (>3x the cap) // will ignore slices that are too small or too large (>3x the cap)
func (bp *bytesPool) Put(b []byte) { func (bp *bytesPool) put(b []byte) {
if c := cap(b); c < bp.w || c > 3*bp.w { if c := cap(b); c < bp.w || c > 3*bp.w {
return return
} }
@ -62,3 +62,40 @@ func (bp *bytesPool) Put(b []byte) {
default: default:
} }
} }
// unsafeBytesPool is a pool for byte slices. It is not safe for concurrent use.
type unsafeBytesPool struct {
items [][]byte
w int
}
// newUnsafeBytesPool creates a new unsafeBytesPool. The sliceCap sets the
// capacity of newly allocated slices, and the nitems determines how many
// items the pool will hold, at maximum.
func newUnsafeBytesPool(sliceCap, nitems int) *unsafeBytesPool {
return &unsafeBytesPool{
items: make([][]byte, 0, nitems),
w: sliceCap,
}
}
// Get returns a slice with pre-allocated space.
func (bp *unsafeBytesPool) get() []byte {
if len(bp.items) > 0 {
last := bp.items[len(bp.items)-1]
bp.items = bp.items[:len(bp.items)-1]
return last
}
return make([]byte, 0, bp.w)
}
// put returns a slice to the pool. This method will ignore slices that are
// too small or too large (>3x the cap)
func (bp *unsafeBytesPool) put(b []byte) {
if c := cap(b); c < bp.w || c > 3*bp.w {
return
}
if len(bp.items) < cap(bp.items) {
bp.items = append(bp.items, b)
}
}

56
trie/list_hasher.go Normal file
View file

@ -0,0 +1,56 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package trie
import (
"bytes"
"github.com/ethereum/go-ethereum/common"
)
// ListHasher is a wrapper of the Merkle-Patricia-Trie, which implements
// types.ListHasher. Compared to a Trie instance, the Update method of this
// type always deep-copies its input slices.
//
// This implementation is very inefficient in terms of memory allocation,
// compared with StackTrie. It exists only for correctness comparison purposes.
type ListHasher struct {
tr *Trie
}
// NewListHasher initializes the list hasher.
func NewListHasher() *ListHasher {
return &ListHasher{
tr: NewEmpty(nil),
}
}
// Reset clears the internal state prepares the ListHasher for reuse.
func (h *ListHasher) Reset() {
h.tr.reset()
}
// Update inserts a key-value pair into the trie.
func (h *ListHasher) Update(key []byte, value []byte) error {
key, value = bytes.Clone(key), bytes.Clone(value)
return h.tr.Update(key, value)
}
// Hash computes the root hash of all inserted key-value pairs.
func (h *ListHasher) Hash() common.Hash {
return h.tr.Hash()
}

View file

@ -28,7 +28,7 @@ import (
var ( var (
stPool = sync.Pool{New: func() any { return new(stNode) }} stPool = sync.Pool{New: func() any { return new(stNode) }}
bPool = newBytesPool(32, 100) bPool = newBytesPool(32, 100)
_ = types.TrieHasher((*StackTrie)(nil)) _ = types.ListHasher((*StackTrie)(nil))
) )
// OnTrieNode is a callback method invoked when a trie node is committed // OnTrieNode is a callback method invoked when a trie node is committed
@ -50,6 +50,7 @@ type StackTrie struct {
onTrieNode OnTrieNode onTrieNode OnTrieNode
kBuf []byte // buf space used for hex-key during insertions kBuf []byte // buf space used for hex-key during insertions
pBuf []byte // buf space used for path during insertions pBuf []byte // buf space used for path during insertions
vPool *unsafeBytesPool
} }
// NewStackTrie allocates and initializes an empty trie. The committed nodes // NewStackTrie allocates and initializes an empty trie. The committed nodes
@ -61,6 +62,7 @@ func NewStackTrie(onTrieNode OnTrieNode) *StackTrie {
onTrieNode: onTrieNode, onTrieNode: onTrieNode,
kBuf: make([]byte, 64), kBuf: make([]byte, 64),
pBuf: make([]byte, 64), pBuf: make([]byte, 64),
vPool: newUnsafeBytesPool(300, 20),
} }
} }
@ -74,6 +76,9 @@ func (t *StackTrie) grow(key []byte) {
} }
// Update inserts a (key, value) pair into the stack trie. // Update inserts a (key, value) pair into the stack trie.
//
// Note the supplied key value pair is copied and managed internally,
// they are safe to be modified after this method returns.
func (t *StackTrie) Update(key, value []byte) error { func (t *StackTrie) Update(key, value []byte) error {
if len(value) == 0 { if len(value) == 0 {
return errors.New("trying to insert empty (deletion)") return errors.New("trying to insert empty (deletion)")
@ -88,7 +93,14 @@ func (t *StackTrie) Update(key, value []byte) error {
} else { } else {
t.last = append(t.last[:0], k...) // reuse key slice t.last = append(t.last[:0], k...) // reuse key slice
} }
t.insert(t.root, k, value, t.pBuf[:0]) vBuf := t.vPool.get()
if cap(vBuf) < len(value) {
vBuf = common.CopyBytes(value)
} else {
vBuf = vBuf[:len(value)]
copy(vBuf, value)
}
t.insert(t.root, k, vBuf, t.pBuf[:0])
return nil return nil
} }
@ -108,14 +120,16 @@ func (t *StackTrie) TrieKey(key []byte) []byte {
// stNode represents a node within a StackTrie // stNode represents a node within a StackTrie
type stNode struct { type stNode struct {
typ uint8 // node type (as in branch, ext, leaf) typ uint8 // node type (as in branch, ext, leaf)
key []byte // key chunk covered by this (leaf|ext) node key []byte // exclusive owned key chunk covered by this (leaf|ext) node
val []byte // value contained by this node if it's a leaf val []byte // exclusive owned value contained by this node (leaf: value; hash: hash)
children [16]*stNode // list of children (for branch and exts) children [16]*stNode // list of children (for branch and ext)
} }
// newLeaf constructs a leaf node with provided node key and value. The key // newLeaf constructs a leaf node with provided node key and value.
// will be deep-copied in the function and safe to modify afterwards, but //
// value is not. // The key is deep-copied within the function, so it can be safely modified
// afterwards. The value is retained directly without copying, as it is
// exclusively owned by the stackTrie.
func newLeaf(key, val []byte) *stNode { func newLeaf(key, val []byte) *stNode {
st := stPool.Get().(*stNode) st := stPool.Get().(*stNode)
st.typ = leafNode st.typ = leafNode
@ -146,9 +160,9 @@ const (
func (n *stNode) reset() *stNode { func (n *stNode) reset() *stNode {
if n.typ == hashedNode { if n.typ == hashedNode {
// On hashnodes, we 'own' the val: it is guaranteed to be not held // On hashnodes, we 'own' the val: it is guaranteed to be not held
// by external caller. Hence, when we arrive here, we can put it back // by external caller. Hence, when we arrive here, we can put it
// into the pool // back into the pool
bPool.Put(n.val) bPool.put(n.val)
} }
n.key = n.key[:0] n.key = n.key[:0]
n.val = nil n.val = nil
@ -172,11 +186,6 @@ func (n *stNode) getDiffIndex(key []byte) int {
} }
// Helper function to that inserts a (key, value) pair into the trie. // Helper function to that inserts a (key, value) pair into the trie.
//
// - The key is not retained by this method, but always copied if needed.
// - The value is retained by this method, as long as the leaf that it represents
// remains unhashed. However: it is never modified.
// - The path is not retained by this method.
func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
switch st.typ { switch st.typ {
case branchNode: /* Branch */ case branchNode: /* Branch */
@ -235,16 +244,14 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
} }
var p *stNode var p *stNode
if diffidx == 0 { if diffidx == 0 {
// the break is on the first byte, so // the break is on the first byte, so the current node
// the current node is converted into // is converted into a branch node.
// a branch node.
st.children[0] = nil st.children[0] = nil
p = st
st.typ = branchNode st.typ = branchNode
p = st
} else { } else {
// the common prefix is at least one byte // the common prefix is at least one byte long, insert
// long, insert a new intermediate branch // a new intermediate branch node.
// node.
st.children[0] = stPool.Get().(*stNode) st.children[0] = stPool.Get().(*stNode)
st.children[0].typ = branchNode st.children[0].typ = branchNode
p = st.children[0] p = st.children[0]
@ -280,8 +287,8 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
if diffidx == 0 { if diffidx == 0 {
// Convert current leaf into a branch // Convert current leaf into a branch
st.typ = branchNode st.typ = branchNode
p = st
st.children[0] = nil st.children[0] = nil
p = st
} else { } else {
// Convert current node into an ext, // Convert current node into an ext,
// and insert a child branch node. // and insert a child branch node.
@ -307,9 +314,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) {
st.val = nil st.val = nil
case emptyNode: /* Empty */ case emptyNode: /* Empty */
st.typ = leafNode *st = *newLeaf(key, value)
st.key = append(st.key, key...) // deep-copy the key as it's volatile
st.val = value
case hashedNode: case hashedNode:
panic("trying to insert into hash") panic("trying to insert into hash")
@ -393,18 +398,23 @@ func (t *StackTrie) hash(st *stNode, path []byte) {
st.typ = hashedNode st.typ = hashedNode
st.key = st.key[:0] st.key = st.key[:0]
st.val = nil // Release reference to potentially externally held slice. // Release reference to value slice which is exclusively owned
// by stackTrie itself.
if cap(st.val) > 0 && t.vPool != nil {
t.vPool.put(st.val)
}
st.val = nil
// Skip committing the non-root node if the size is smaller than 32 bytes // Skip committing the non-root node if the size is smaller than 32 bytes
// as tiny nodes are always embedded in their parent except root node. // as tiny nodes are always embedded in their parent except root node.
if len(blob) < 32 && len(path) > 0 { if len(blob) < 32 && len(path) > 0 {
st.val = bPool.GetWithSize(len(blob)) st.val = bPool.getWithSize(len(blob))
copy(st.val, blob) copy(st.val, blob)
return return
} }
// Write the hash to the 'val'. We allocate a new val here to not mutate // Write the hash to the 'val'. We allocate a new val here to not mutate
// input values. // input values.
st.val = bPool.GetWithSize(32) st.val = bPool.getWithSize(32)
t.h.hashDataTo(st.val, blob) t.h.hashDataTo(st.val, blob)
// Invoke the callback it's provided. Notably, the path and blob slices are // Invoke the callback it's provided. Notably, the path and blob slices are

View file

@ -784,8 +784,8 @@ func (t *Trie) Witness() map[string][]byte {
return t.prevalueTracer.Values() return t.prevalueTracer.Values()
} }
// Reset drops the referenced root node and cleans all internal state. // reset drops the referenced root node and cleans all internal state.
func (t *Trie) Reset() { func (t *Trie) reset() {
t.root = nil t.root = nil
t.owner = common.Hash{} t.owner = common.Hash{}
t.unhashed = 0 t.unhashed = 0