diff --git a/core/types/hashing.go b/core/types/hashing.go index 8337f656bc..98fe64e15a 100644 --- a/core/types/hashing.go +++ b/core/types/hashing.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -// hasherPool holds LegacyKeccak256 hashers for rlpHash. +// hasherPool holds LegacyKeccak256 buffer for rlpHash. var hasherPool = sync.Pool{ New: func() interface{} { return crypto.NewKeccakState() }, } diff --git a/core/types/hashing_test.go b/core/types/hashing_test.go index 8f51088179..a7153bf09a 100644 --- a/core/types/hashing_test.go +++ b/core/types/hashing_test.go @@ -74,15 +74,25 @@ 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) { txs, err := genTxs(200) if err != nil { b.Fatal(err) } want := types.DeriveSha(txs, trie.NewListHasher()) - var have common.Hash + b.Run("std_trie", func(b *testing.B) { b.ReportAllocs() + var have common.Hash for b.Loop() { have = types.DeriveSha(txs, trie.NewListHasher()) } @@ -94,6 +104,7 @@ func BenchmarkDeriveSha200(b *testing.B) { st := trie.NewStackTrie(nil) b.Run("stack_trie", func(b *testing.B) { b.ReportAllocs() + var have common.Hash for b.Loop() { st.Reset() have = types.DeriveSha(txs, st) diff --git a/trie/bytepool.go b/trie/bytepool.go index 9a647e5fd8..31be7ae749 100644 --- a/trie/bytepool.go +++ b/trie/bytepool.go @@ -79,7 +79,7 @@ func newUnsafeBytesPool(sliceCap, nitems int) *unsafeBytesPool { } } -// Get returns a slice. +// 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] @@ -89,8 +89,8 @@ func (bp *unsafeBytesPool) get() []byte { 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) +// 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 diff --git a/trie/stacktrie.go b/trie/stacktrie.go index ed7c7635ba..18fe1eea78 100644 --- a/trie/stacktrie.go +++ b/trie/stacktrie.go @@ -77,8 +77,8 @@ func (t *StackTrie) grow(key []byte) { // 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. +// 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 { if len(value) == 0 { return errors.New("trying to insert empty (deletion)") @@ -120,14 +120,16 @@ func (t *StackTrie) TrieKey(key []byte) []byte { // stNode represents a node within a StackTrie type stNode struct { typ uint8 // node type (as in branch, ext, leaf) - key []byte // key chunk covered by this (leaf|ext) node - val []byte // value contained by this node if it's a leaf - children [16]*stNode // list of children (for branch and exts) + key []byte // exclusive owned key chunk covered by this (leaf|ext) node + val []byte // exclusive owned value contained by this node (leaf: value; hash: hash) + children [16]*stNode // list of children (for branch and ext) } -// newLeaf constructs a leaf node with provided node key and value. The key -// will be deep-copied in the function and safe to modify afterwards, but -// value is not. +// newLeaf constructs a leaf node with provided node key and value. +// +// 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 { st := stPool.Get().(*stNode) st.typ = leafNode @@ -158,8 +160,8 @@ const ( func (n *stNode) reset() *stNode { if n.typ == hashedNode { // 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 - // into the pool + // by external caller. Hence, when we arrive here, we can put it + // back into the pool bPool.put(n.val) } n.key = n.key[:0] @@ -184,11 +186,6 @@ func (n *stNode) getDiffIndex(key []byte) int { } // 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) { switch st.typ { case branchNode: /* Branch */ @@ -247,16 +244,14 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { } var p *stNode if diffidx == 0 { - // the break is on the first byte, so - // the current node is converted into - // a branch node. + // the break is on the first byte, so the current node + // is converted into a branch node. st.children[0] = nil - p = st st.typ = branchNode + p = st } else { - // the common prefix is at least one byte - // long, insert a new intermediate branch - // node. + // the common prefix is at least one byte long, insert + // a new intermediate branch node. st.children[0] = stPool.Get().(*stNode) st.children[0].typ = branchNode p = st.children[0] @@ -292,8 +287,8 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { if diffidx == 0 { // Convert current leaf into a branch st.typ = branchNode - p = st st.children[0] = nil + p = st } else { // Convert current node into an ext, // and insert a child branch node. @@ -319,9 +314,7 @@ func (t *StackTrie) insert(st *stNode, key, value []byte, path []byte) { st.val = nil case emptyNode: /* Empty */ - st.typ = leafNode - st.key = append(st.key, key...) // deep-copy the key as it's volatile - st.val = value + *st = *newLeaf(key, value) case hashedNode: panic("trying to insert into hash") @@ -405,7 +398,8 @@ func (t *StackTrie) hash(st *stNode, path []byte) { st.typ = hashedNode st.key = st.key[:0] - // Release reference to (potentially externally held) value-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) }