Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trie: reduce hasher allocations #16896

Merged
merged 2 commits into from
Jun 5, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions trie/hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package trie

import (
"bytes"
"hash"
"sync"

Expand All @@ -27,17 +26,39 @@ import (
)

type hasher struct {
tmp *bytes.Buffer
sha hash.Hash
tmp sliceBuffer
sha keccakState
cachegen uint16
cachelimit uint16
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.
var hasherPool = sync.Pool{
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),
}
},
}

Expand Down Expand Up @@ -157,26 +178,23 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
}
// Generate the RLP encoding of the node
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())
}
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
}
// Larger nodes are replaced by their hash and stored in the database.
hash, _ := n.cache()
if hash == nil {
h.sha.Reset()
h.sha.Write(h.tmp.Bytes())
hash = hashNode(h.sha.Sum(nil))
hash = h.makeHashNode(h.tmp)
}

if db != nil {
// We are pooling the trie nodes into an intermediate memory cache
db.lock.Lock()

hash := common.BytesToHash(hash)
db.insert(hash, h.tmp.Bytes())

db.insert(hash, h.tmp)
// Track all direct parent->child node references
switch n := n.(type) {
case *shortNode:
Expand Down Expand Up @@ -210,3 +228,11 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
}
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
}