mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
poc/squashme: test paralell trie hashing
This commit is contained in:
parent
40789748f7
commit
a8157cba59
6 changed files with 142 additions and 30 deletions
120
trie/hasher.go
120
trie/hasher.go
|
|
@ -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,15 +159,77 @@ 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
|
||||||
|
}
|
||||||
|
|
||||||
|
if e3 != nil {
|
||||||
|
return original, original, e3
|
||||||
|
}
|
||||||
|
|
||||||
|
if e4 != nil {
|
||||||
|
return original, original, e4
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
if n.Children[i] != nil {
|
if n.Children[i] != nil {
|
||||||
collapsed.Children[i], cached.Children[i], err = h.hash(n.Children[i], db, false)
|
collapsed.Children[i], cached.Children[i], err = h.hashParalell(n.Children[i], db, false, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return original, original, err
|
return original, original, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
cached.Children[16] = n.Children[16]
|
cached.Children[16] = n.Children[16]
|
||||||
return collapsed, cached, nil
|
return collapsed, cached, nil
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue