trie: reduce hasher allocations (#16896)

* trie: reduce hasher allocations

name    old time/op    new time/op    delta
Hash-8    4.05µs ±12%    3.56µs ± 9%  -12.13%  (p=0.000 n=20+19)

name    old alloc/op   new alloc/op   delta
Hash-8    1.30kB ± 0%    0.66kB ± 0%  -49.15%  (p=0.000 n=20+20)

name    old allocs/op  new allocs/op  delta
Hash-8      11.0 ± 0%       8.0 ± 0%  -27.27%  (p=0.000 n=20+20)

* trie: bump initial buffer cap in hasher
This commit is contained in:
Felix Lange 2018-06-05 14:06:29 +02:00 committed by Péter Szilágyi
parent 5bee5d69d7
commit e8ea5aa0d5

View File

@ -17,7 +17,6 @@
package trie package trie
import ( import (
"bytes"
"hash" "hash"
"sync" "sync"
@ -27,17 +26,39 @@ import (
) )
type hasher struct { type hasher struct {
tmp *bytes.Buffer tmp sliceBuffer
sha hash.Hash sha keccakState
cachegen uint16 cachegen uint16
cachelimit uint16 cachelimit uint16
onleaf LeafCallback onleaf LeafCallback
} }
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
// Read to get a variable amount of data from the hash state. Read is faster than Sum
// because it doesn't copy the internal state, but also modifies the internal state.
type keccakState interface {
hash.Hash
Read([]byte) (int, error)
}
type sliceBuffer []byte
func (b *sliceBuffer) Write(data []byte) (n int, err error) {
*b = append(*b, data...)
return len(data), nil
}
func (b *sliceBuffer) Reset() {
*b = (*b)[:0]
}
// hashers live in a global db. // hashers live in a global db.
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return &hasher{tmp: new(bytes.Buffer), sha: sha3.NewKeccak256()} return &hasher{
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
sha: sha3.NewKeccak256().(keccakState),
}
}, },
} }
@ -157,26 +178,23 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
} }
// Generate the RLP encoding of the node // Generate the RLP encoding of the node
h.tmp.Reset() h.tmp.Reset()
if err := rlp.Encode(h.tmp, n); err != nil { if err := rlp.Encode(&h.tmp, n); err != nil {
panic("encode error: " + err.Error()) panic("encode error: " + err.Error())
} }
if h.tmp.Len() < 32 && !force { if len(h.tmp) < 32 && !force {
return n, nil // Nodes smaller than 32 bytes are stored inside their parent return n, nil // Nodes smaller than 32 bytes are stored inside their parent
} }
// Larger nodes are replaced by their hash and stored in the database. // Larger nodes are replaced by their hash and stored in the database.
hash, _ := n.cache() hash, _ := n.cache()
if hash == nil { if hash == nil {
h.sha.Reset() hash = h.makeHashNode(h.tmp)
h.sha.Write(h.tmp.Bytes())
hash = hashNode(h.sha.Sum(nil))
} }
if db != nil { if db != nil {
// We are pooling the trie nodes into an intermediate memory cache // We are pooling the trie nodes into an intermediate memory cache
db.lock.Lock() db.lock.Lock()
hash := common.BytesToHash(hash) hash := common.BytesToHash(hash)
db.insert(hash, h.tmp.Bytes()) db.insert(hash, h.tmp)
// Track all direct parent->child node references // Track all direct parent->child node references
switch n := n.(type) { switch n := n.(type) {
case *shortNode: case *shortNode:
@ -210,3 +228,11 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
} }
return hash, nil return hash, nil
} }
func (h *hasher) makeHashNode(data []byte) hashNode {
n := make(hashNode, h.sha.Size())
h.sha.Reset()
h.sha.Write(data)
h.sha.Read(n)
return n
}