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 {
|
||||
tmp sliceBuffer
|
||||
sha keccakState
|
||||
tmp []sliceBuffer
|
||||
sha []keccakState
|
||||
onleaf LeafCallback
|
||||
}
|
||||
|
||||
|
|
@ -54,8 +54,18 @@ func (b *sliceBuffer) Reset() {
|
|||
var hasherPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &hasher{
|
||||
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
|
||||
sha: sha3.NewLegacyKeccak256().(keccakState),
|
||||
tmp: []sliceBuffer{
|
||||
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
|
||||
// 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) {
|
||||
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 hash, dirty := n.cache(); hash != 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
|
||||
collapsed, cached, err := h.hashChildren(n, db)
|
||||
collapsed, cached, err := h.hashChildrenParalell(n, db, id)
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
// as a replacement for the original node with the child hashes cached in.
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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 {
|
||||
return original, original, err
|
||||
}
|
||||
|
|
@ -139,15 +159,77 @@ func (h *hasher) hashChildren(original node, db *Database) (node, node, error) {
|
|||
case *fullNode:
|
||||
// Hash the full node's children, caching the newly hashed subtrees
|
||||
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++ {
|
||||
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 {
|
||||
return original, original, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cached.Children[16] = n.Children[16]
|
||||
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
|
||||
// the key/value pair to it and tracks any node->child references as well as any
|
||||
// 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.
|
||||
if _, isHash := n.(hashNode); n == nil || isHash {
|
||||
return n, nil
|
||||
|
|
@ -169,22 +251,22 @@ func (h *hasher) store(n node, db *Database, force bool) (node, error) {
|
|||
hash, _ := n.cache()
|
||||
if hash == nil {
|
||||
// Generate the RLP encoding of the node
|
||||
h.tmp.Reset()
|
||||
if err := rlp.Encode(&h.tmp, n); err != nil {
|
||||
h.tmp[id].Reset()
|
||||
if err := rlp.Encode(&h.tmp[id], n); err != nil {
|
||||
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
|
||||
}
|
||||
// Larger nodes are replaced by their hash and stored in the database.
|
||||
hash = h.makeHashNode(h.tmp)
|
||||
hash = h.makeHashNode(id)
|
||||
}
|
||||
if db != nil {
|
||||
// We are pooling the trie nodes into an intermediate memory cache
|
||||
hash := common.BytesToHash(hash)
|
||||
|
||||
db.lock.Lock()
|
||||
db.insert(hash, h.tmp, n)
|
||||
db.insert(hash, h.tmp[id], n)
|
||||
db.lock.Unlock()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (h *hasher) makeHashNode(data []byte) hashNode {
|
||||
n := make(hashNode, h.sha.Size())
|
||||
h.sha.Reset()
|
||||
h.sha.Write(data)
|
||||
h.sha.Read(n)
|
||||
func (h *hasher) makeHashNode(id int) hashNode {
|
||||
n := make(hashNode, h.sha[id].Size())
|
||||
h.sha[id].Reset()
|
||||
h.sha[id].Write(h.tmp[id])
|
||||
h.sha[id].Read(n)
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ func (it *nodeIterator) LeafProof() [][]byte {
|
|||
for i, item := range it.stack[:len(it.stack)-1] {
|
||||
// Gather nodes that end up as hash nodes (or the root)
|
||||
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 {
|
||||
enc, _ := rlp.EncodeToBytes(node)
|
||||
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
|
||||
// if encoding doesn't work and we're not writing to any database.
|
||||
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 the node's database encoding is a hash (or is the
|
||||
// root node), it becomes a proof element.
|
||||
|
|
@ -80,7 +80,7 @@ func (t *Trie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) e
|
|||
} else {
|
||||
enc, _ := rlp.EncodeToBytes(n)
|
||||
if !ok {
|
||||
hash = hasher.makeHashNode(enc)
|
||||
hash = hasher.makeHashNode(0)
|
||||
}
|
||||
proofDb.Put(hash, enc)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,9 +177,9 @@ func (t *SecureTrie) NodeIterator(start []byte) NodeIterator {
|
|||
// invalid on the next call to hashKey or secKey.
|
||||
func (t *SecureTrie) hashKey(key []byte) []byte {
|
||||
h := newHasher(nil)
|
||||
h.sha.Reset()
|
||||
h.sha.Write(key)
|
||||
buf := h.sha.Sum(t.hashKeyBuf[:0])
|
||||
h.sha[0].Reset()
|
||||
h.sha[0].Write(key)
|
||||
buf := h.sha[0].Sum(t.hashKeyBuf[:0])
|
||||
returnHasherToPool(h)
|
||||
return buf
|
||||
}
|
||||
|
|
|
|||
|
|
@ -429,5 +429,5 @@ func (t *Trie) hashRoot(db *Database, onleaf LeafCallback) (node, node, error) {
|
|||
}
|
||||
h := newHasher(onleaf)
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
dir, err := ioutil.TempDir("", "trie-bench")
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue