poc/squashme: test paralell trie hashing

This commit is contained in:
Martin Holst Swende 2019-12-16 15:49:54 +01:00
parent 40789748f7
commit a8157cba59
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
6 changed files with 142 additions and 30 deletions

View file

@ -26,8 +26,8 @@ import (
) )
type hasher struct { type hasher struct {
tmp sliceBuffer tmp []sliceBuffer
sha keccakState sha []keccakState
onleaf LeafCallback onleaf LeafCallback
} }
@ -54,8 +54,18 @@ func (b *sliceBuffer) Reset() {
var hasherPool = sync.Pool{ var hasherPool = sync.Pool{
New: func() interface{} { New: func() interface{} {
return &hasher{ return &hasher{
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode. tmp: []sliceBuffer{
sha: sha3.NewLegacyKeccak256().(keccakState), make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
},
sha: []keccakState{
sha3.NewLegacyKeccak256().(keccakState),
sha3.NewLegacyKeccak256().(keccakState),
sha3.NewLegacyKeccak256().(keccakState),
sha3.NewLegacyKeccak256().(keccakState),
},
} }
}, },
} }
@ -73,6 +83,9 @@ func returnHasherToPool(h *hasher) {
// hash collapses a node down into a hash node, also returning a copy of the // hash collapses a node down into a hash node, also returning a copy of the
// original node initialized with the computed hash to replace the original one. // original node initialized with the computed hash to replace the original one.
func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) { func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) {
return h.hashParalell(n, db, force, 0)
}
func (h *hasher) hashParalell(n node, db *Database, force bool, id int) (node, node, error) {
// If we're not storing the node, just hashing, use available cached data // If we're not storing the node, just hashing, use available cached data
if hash, dirty := n.cache(); hash != nil { if hash, dirty := n.cache(); hash != nil {
if db == nil { if db == nil {
@ -88,11 +101,14 @@ func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) {
} }
} }
// Trie not processed yet or needs storage, walk the children // Trie not processed yet or needs storage, walk the children
collapsed, cached, err := h.hashChildren(n, db) collapsed, cached, err := h.hashChildrenParalell(n, db, id)
if err != nil { if err != nil {
return hashNode{}, n, err return hashNode{}, n, err
} }
hashed, err := h.store(collapsed, db, force) if id == -1 {
id = 0
}
hashed, err := h.store(collapsed, db, force, id)
if err != nil { if err != nil {
return hashNode{}, n, err return hashNode{}, n, err
} }
@ -119,6 +135,10 @@ func (h *hasher) hash(n node, db *Database, force bool) (node, node, error) {
// size of the child is larger than a hash, returning the collapsed node as well // size of the child is larger than a hash, returning the collapsed node as well
// as a replacement for the original node with the child hashes cached in. // as a replacement for the original node with the child hashes cached in.
func (h *hasher) hashChildren(original node, db *Database) (node, node, error) { func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
return h.hashChildrenParalell(original, db, 0)
}
func (h *hasher) hashChildrenParalell(original node, db *Database, id int) (node, node, error) {
var err error var err error
switch n := original.(type) { switch n := original.(type) {
@ -129,7 +149,7 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
cached.Key = common.CopyBytes(n.Key) cached.Key = common.CopyBytes(n.Key)
if _, ok := n.Val.(valueNode); !ok { if _, ok := n.Val.(valueNode); !ok {
collapsed.Val, cached.Val, err = h.hash(n.Val, db, false) collapsed.Val, cached.Val, err = h.hashParalell(n.Val, db, false, id)
if err != nil { if err != nil {
return original, original, err return original, original, err
} }
@ -139,12 +159,74 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
case *fullNode: case *fullNode:
// Hash the full node's children, caching the newly hashed subtrees // Hash the full node's children, caching the newly hashed subtrees
collapsed, cached := n.copy(), n.copy() collapsed, cached := n.copy(), n.copy()
if id == -1 { // Top level, thread out
var wg sync.WaitGroup
wg.Add(3)
var e1, e2, e3, e4 error
go func() {
for i := 0; i < 4; i++ {
if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], e1 = h.hashParalell(n.Children[i], db, false, 0)
if err != nil {
return
}
}
}
wg.Done()
}()
go func() {
for i := 4; i < 8; i++ {
if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], e2 = h.hashParalell(n.Children[i], db, false, 1)
if err != nil {
return
}
}
}
wg.Done()
}()
go func() {
for i := 8; i < 12; i++ {
if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], e3 = h.hashParalell(n.Children[i], db, false, 2)
if err != nil {
return
}
}
}
wg.Done()
}()
for i := 12; i < 16; i++ {
if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], e4 = h.hashParalell(n.Children[i], db, false, 3)
if err != nil {
break
}
}
}
wg.Wait()
if e1 != nil {
return original, original, e1
}
if e2 != nil {
return original, original, e2
}
for i := 0; i < 16; i++ { if e3 != nil {
if n.Children[i] != nil { return original, original, e3
collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false) }
if err != nil {
return original, original, err if e4 != nil {
return original, original, e4
}
} else {
for i := 0; i < 16; i++ {
if n.Children[i] != nil {
collapsed.Children[i], cached.Children[i], err = h.hashParalell(n.Children[i], db, false, id)
if err != nil {
return original, original, err
}
} }
} }
} }
@ -160,7 +242,7 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
// store hashes the node n and if we have a storage layer specified, it writes // store hashes the node n and if we have a storage layer specified, it writes
// the key/value pair to it and tracks any node->child references as well as any // the key/value pair to it and tracks any node->child references as well as any
// node->external trie references. // node->external trie references.
func (h *hasher) store(n node, db *Database, force bool) (node, error) { func (h *hasher) store(n node, db *Database, force bool, id int) (node, error) {
// Don't store hashes or empty nodes. // Don't store hashes or empty nodes.
if _, isHash := n.(hashNode); n == nil || isHash { if _, isHash := n.(hashNode); n == nil || isHash {
return n, nil return n, nil
@ -169,22 +251,22 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
hash, _ := n.cache() hash, _ := n.cache()
if hash == nil { if hash == nil {
// Generate the RLP encoding of the node // Generate the RLP encoding of the node
h.tmp.Reset() h.tmp[id].Reset()
if err := rlp.Encode(&h.tmp, n); err != nil { if err := rlp.Encode(&h.tmp[id], n); err != nil {
panic("encode error: " + err.Error()) panic("encode error: " + err.Error())
} }
if len(h.tmp) < 32 && !force { if len(h.tmp[id]) < 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 = h.makeHashNode(h.tmp) hash = h.makeHashNode(id)
} }
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
hash := common.BytesToHash(hash) hash := common.BytesToHash(hash)
db.lock.Lock() db.lock.Lock()
db.insert(hash, h.tmp, n) db.insert(hash, h.tmp[id], n)
db.lock.Unlock() db.lock.Unlock()
// Track external references from account->storage trie // Track external references from account->storage trie
@ -206,10 +288,10 @@ 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 { func (h *hasher) makeHashNode(id int) hashNode {
n := make(hashNode, h.sha.Size()) n := make(hashNode, h.sha[id].Size())
h.sha.Reset() h.sha[id].Reset()
h.sha.Write(data) h.sha[id].Write(h.tmp[id])
h.sha.Read(n) h.sha[id].Read(n)
return n return n
} }

View file

@ -190,7 +190,7 @@ func (it *nodeIterator) LeafProof() [][]byte {
for i, item := range it.stack[:len(it.stack)-1] { for i, item := range it.stack[:len(it.stack)-1] {
// Gather nodes that end up as hash nodes (or the root) // Gather nodes that end up as hash nodes (or the root)
node, _, _ := hasher.hashChildren(item.node, nil) node, _, _ := hasher.hashChildren(item.node, nil)
hashed, _ := hasher.store(node, nil, false) hashed, _ := hasher.store(node, nil, false,0)
if _, ok := hashed.(hashNode); ok || i == 0 { if _, ok := hashed.(hashNode); ok || i == 0 {
enc, _ := rlp.EncodeToBytes(node) enc, _ := rlp.EncodeToBytes(node)
proofs = append(proofs, enc) proofs = append(proofs, enc)

View file

@ -71,7 +71,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
// Don't bother checking for errors here since hasher panics // Don't bother checking for errors here since hasher panics
// if encoding doesn't work and we're not writing to any database. // if encoding doesn't work and we're not writing to any database.
n, _, _ = hasher.hashChildren(n, nil) n, _, _ = hasher.hashChildren(n, nil)
hn, _ := hasher.store(n, nil, false) hn, _ := hasher.store(n, nil, false, 0)
if hash, ok := hn.(hashNode); ok || i == 0 { if hash, ok := hn.(hashNode); ok || i == 0 {
// If the node's database encoding is a hash (or is the // If the node's database encoding is a hash (or is the
// root node), it becomes a proof element. // root node), it becomes a proof element.
@ -80,7 +80,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
} else { } else {
enc, _ := rlp.EncodeToBytes(n) enc, _ := rlp.EncodeToBytes(n)
if !ok { if !ok {
hash = hasher.makeHashNode(enc) hash = hasher.makeHashNode(0)
} }
proofDb.Put(hash, enc) proofDb.Put(hash, enc)
} }

View file

@ -177,9 +177,9 @@ func (t *SecureTrie) NodeIterator(start []byte) NodeIterator {
// invalid on the next call to hashKey or secKey. // invalid on the next call to hashKey or secKey.
func (t *SecureTrie) hashKey(key []byte) []byte { func (t *SecureTrie) hashKey(key []byte) []byte {
h := newHasher(nil) h := newHasher(nil)
h.sha.Reset() h.sha[0].Reset()
h.sha.Write(key) h.sha[0].Write(key)
buf := h.sha.Sum(t.hashKeyBuf[:0]) buf := h.sha[0].Sum(t.hashKeyBuf[:0])
returnHasherToPool(h) returnHasherToPool(h)
return buf return buf
} }

View file

@ -429,5 +429,5 @@ func (t *Trie) hashRoot(db *Database, onleaf LeafCallback) (node, node, error) {
} }
h := newHasher(onleaf) h := newHasher(onleaf)
defer returnHasherToPool(h) defer returnHasherToPool(h)
return h.hash(t.root, db, true) return h.hashParalell(t.root, db, true, -1)
} }

View file

@ -548,6 +548,36 @@ func BenchmarkCommitAfterHash(b *testing.B) {
trie.Commit(nil) trie.Commit(nil)
} }
func TestCommitAfterHash(t *testing.T) {
// Make the random benchmark deterministic
random := rand.New(rand.NewSource(0))
// Create a realistic account trie to hash
addresses := make([][20]byte, 10000)
for i := 0; i < len(addresses); i++ {
for j := 0; j < len(addresses[i]); j++ {
addresses[i][j] = byte(random.Intn(256))
}
}
accounts := make([][]byte, len(addresses))
for i := 0; i < len(accounts); i++ {
var (
nonce = uint64(random.Int63())
balance = new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil))
root = emptyRoot
code = crypto.Keccak256(nil)
)
accounts[i], _ = rlp.EncodeToBytes([]interface{}{nonce, balance, root, code})
}
// Insert the accounts into the trie and hash it
trie := newEmpty()
for i := 0; i < len(addresses); i++ {
trie.Update(crypto.Keccak256(addresses[i][:]), accounts[i])
}
trie.Hash()
trie.Commit(nil)
}
func tempDB() (string, *Database) { func tempDB() (string, *Database) {
dir, err := ioutil.TempDir("", "trie-bench") dir, err := ioutil.TempDir("", "trie-bench")
if err != nil { if err != nil {