mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 02:12:23 +00:00
trie: parallize committer
This commit is contained in:
parent
65e5ca7d81
commit
633d376289
6 changed files with 130 additions and 47 deletions
|
|
@ -945,7 +945,7 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
|
||||||
slots = make(map[common.Hash][]byte)
|
slots = make(map[common.Hash][]byte)
|
||||||
)
|
)
|
||||||
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||||
nodes.AddNode(path, trienode.NewDeleted())
|
nodes.AddNode(string(path), trienode.NewDeleted())
|
||||||
})
|
})
|
||||||
for iter.Next() {
|
for iter.Next() {
|
||||||
slot := common.CopyBytes(iter.Slot())
|
slot := common.CopyBytes(iter.Slot())
|
||||||
|
|
@ -991,7 +991,7 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r
|
||||||
if it.Hash() == (common.Hash{}) {
|
if it.Hash() == (common.Hash{}) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nodes.AddNode(it.Path(), trienode.NewDeleted())
|
nodes.AddNode(string(it.Path()), trienode.NewDeleted())
|
||||||
}
|
}
|
||||||
if err := it.Error(); err != nil {
|
if err := it.Error(); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,8 @@ package trie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
|
|
@ -30,28 +32,44 @@ type committer struct {
|
||||||
nodes *trienode.NodeSet
|
nodes *trienode.NodeSet
|
||||||
tracer *tracer
|
tracer *tracer
|
||||||
collectLeaf bool
|
collectLeaf bool
|
||||||
|
parallel bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// newCommitter creates a new committer or picks one from the pool.
|
// newCommitter creates a new committer or picks one from the pool.
|
||||||
func newCommitter(nodeset *trienode.NodeSet, tracer *tracer, collectLeaf bool) *committer {
|
func newCommitter(nodes *trienode.NodeSet, tracer *tracer, collectLeaf bool, parallel bool) *committer {
|
||||||
return &committer{
|
return &committer{
|
||||||
nodes: nodeset,
|
nodes: nodes,
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
collectLeaf: collectLeaf,
|
collectLeaf: collectLeaf,
|
||||||
|
parallel: parallel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type wrapNode struct {
|
||||||
|
node *trienode.Node
|
||||||
|
path string
|
||||||
|
leafHash common.Hash // optional, the parent hash of the relative leaf
|
||||||
|
leafBlob []byte // optional, the blob of the relative leaf
|
||||||
|
}
|
||||||
|
|
||||||
// Commit collapses a node down into a hash node.
|
// Commit collapses a node down into a hash node.
|
||||||
func (c *committer) Commit(n node) hashNode {
|
func (c *committer) Commit(n node) hashNode {
|
||||||
return c.commit(nil, n).(hashNode)
|
hn, wnodes := c.commit(nil, n, true)
|
||||||
|
for _, wn := range wnodes {
|
||||||
|
c.nodes.AddNode(wn.path, wn.node)
|
||||||
|
if wn.leafHash != (common.Hash{}) {
|
||||||
|
c.nodes.AddLeaf(wn.leafHash, wn.leafBlob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hn.(hashNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit collapses a node down into a hash node and returns it.
|
// commit collapses a node down into a hash node and returns it.
|
||||||
func (c *committer) commit(path []byte, n node) node {
|
func (c *committer) commit(path []byte, n node, topmost bool) (node, []*wrapNode) {
|
||||||
// if this path is clean, use available cached data
|
// if this path is clean, use available cached data
|
||||||
hash, dirty := n.cache()
|
hash, dirty := n.cache()
|
||||||
if hash != nil && !dirty {
|
if hash != nil && !dirty {
|
||||||
return hash
|
return hash, nil
|
||||||
}
|
}
|
||||||
// Commit children, then parent, and remove the dirty flag.
|
// Commit children, then parent, and remove the dirty flag.
|
||||||
switch cn := n.(type) {
|
switch cn := n.(type) {
|
||||||
|
|
@ -61,38 +79,72 @@ func (c *committer) commit(path []byte, n node) node {
|
||||||
|
|
||||||
// If the child is fullNode, recursively commit,
|
// If the child is fullNode, recursively commit,
|
||||||
// otherwise it can only be hashNode or valueNode.
|
// otherwise it can only be hashNode or valueNode.
|
||||||
|
var nodes []*wrapNode
|
||||||
if _, ok := cn.Val.(*fullNode); ok {
|
if _, ok := cn.Val.(*fullNode); ok {
|
||||||
collapsed.Val = c.commit(append(path, cn.Key...), cn.Val)
|
collapsed.Val, nodes = c.commit(append(path, cn.Key...), cn.Val, false)
|
||||||
}
|
}
|
||||||
// The key needs to be copied, since we're adding it to the
|
// The key needs to be copied, since we're adding it to the
|
||||||
// modified nodeset.
|
// modified nodeset.
|
||||||
collapsed.Key = hexToCompact(cn.Key)
|
collapsed.Key = hexToCompact(cn.Key)
|
||||||
hashedNode := c.store(path, collapsed)
|
hashedNode, wNode := c.store(path, collapsed)
|
||||||
if hn, ok := hashedNode.(hashNode); ok {
|
if wNode != nil {
|
||||||
return hn
|
nodes = append(nodes, wNode)
|
||||||
}
|
}
|
||||||
return collapsed
|
if hn, ok := hashedNode.(hashNode); ok {
|
||||||
|
return hn, nodes
|
||||||
|
}
|
||||||
|
return collapsed, nodes
|
||||||
case *fullNode:
|
case *fullNode:
|
||||||
hashedKids := c.commitChildren(path, cn)
|
hashedKids, nodes := c.commitChildren(path, cn, topmost && c.parallel)
|
||||||
collapsed := cn.copy()
|
collapsed := cn.copy()
|
||||||
collapsed.Children = hashedKids
|
collapsed.Children = hashedKids
|
||||||
|
|
||||||
hashedNode := c.store(path, collapsed)
|
hashedNode, wNode := c.store(path, collapsed)
|
||||||
if hn, ok := hashedNode.(hashNode); ok {
|
if wNode != nil {
|
||||||
return hn
|
nodes = append(nodes, wNode)
|
||||||
}
|
}
|
||||||
return collapsed
|
if hn, ok := hashedNode.(hashNode); ok {
|
||||||
|
return hn, nodes
|
||||||
|
}
|
||||||
|
return collapsed, nodes
|
||||||
case hashNode:
|
case hashNode:
|
||||||
return cn
|
return cn, nil
|
||||||
default:
|
default:
|
||||||
// nil, valuenode shouldn't be committed
|
// nil, valuenode shouldn't be committed
|
||||||
panic(fmt.Sprintf("%T: invalid node: %v", n, n))
|
panic(fmt.Sprintf("%T: invalid node: %v", n, n))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type task struct {
|
||||||
|
node node
|
||||||
|
index int
|
||||||
|
path []byte
|
||||||
|
}
|
||||||
|
|
||||||
// commitChildren commits the children of the given fullnode
|
// commitChildren commits the children of the given fullnode
|
||||||
func (c *committer) commitChildren(path []byte, n *fullNode) [17]node {
|
func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) ([17]node, []*wrapNode) {
|
||||||
var children [17]node
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
children [17]node
|
||||||
|
results [16][]*wrapNode
|
||||||
|
tasks = make(chan task)
|
||||||
|
)
|
||||||
|
if parallel {
|
||||||
|
worker := func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for t := range tasks {
|
||||||
|
children[t.index], results[t.index] = c.commit(t.path, t.node, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threads := runtime.NumCPU()
|
||||||
|
if threads > 16 {
|
||||||
|
threads = 16
|
||||||
|
}
|
||||||
|
for i := 0; i < threads; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go worker()
|
||||||
|
}
|
||||||
|
}
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
child := n.Children[i]
|
child := n.Children[i]
|
||||||
if child == nil {
|
if child == nil {
|
||||||
|
|
@ -108,18 +160,36 @@ func (c *committer) commitChildren(path []byte, n *fullNode) [17]node {
|
||||||
// Commit the child recursively and store the "hashed" value.
|
// Commit the child recursively and store the "hashed" value.
|
||||||
// Note the returned node can be some embedded nodes, so it's
|
// Note the returned node can be some embedded nodes, so it's
|
||||||
// possible the type is not hashNode.
|
// possible the type is not hashNode.
|
||||||
children[i] = c.commit(append(path, byte(i)), child)
|
if !parallel {
|
||||||
|
children[i], results[i] = c.commit(append(path, byte(i)), child, false)
|
||||||
|
} else {
|
||||||
|
tasks <- task{
|
||||||
|
index: i,
|
||||||
|
node: child,
|
||||||
|
path: append(path, byte(i)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parallel {
|
||||||
|
close(tasks)
|
||||||
|
wg.Wait()
|
||||||
}
|
}
|
||||||
// For the 17th child, it's possible the type is valuenode.
|
// For the 17th child, it's possible the type is valuenode.
|
||||||
if n.Children[16] != nil {
|
if n.Children[16] != nil {
|
||||||
children[16] = n.Children[16]
|
children[16] = n.Children[16]
|
||||||
}
|
}
|
||||||
return children
|
var wnodes []*wrapNode
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
if results[i] != nil {
|
||||||
|
wnodes = append(wnodes, results[i]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return children, wnodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// store hashes the node n and adds it to the modified nodeset. If leaf collection
|
// store hashes the node n and adds it to the modified nodeset. If leaf collection
|
||||||
// is enabled, leaf nodes will be tracked in the modified nodeset as well.
|
// is enabled, leaf nodes will be tracked in the modified nodeset as well.
|
||||||
func (c *committer) store(path []byte, n node) node {
|
func (c *committer) store(path []byte, n node) (node, *wrapNode) {
|
||||||
// Larger nodes are replaced by their hash and stored in the database.
|
// Larger nodes are replaced by their hash and stored in the database.
|
||||||
var hash, _ = n.cache()
|
var hash, _ = n.cache()
|
||||||
|
|
||||||
|
|
@ -133,25 +203,33 @@ func (c *committer) store(path []byte, n node) node {
|
||||||
// deleted only if the node was existent in database before.
|
// deleted only if the node was existent in database before.
|
||||||
_, ok := c.tracer.accessList[string(path)]
|
_, ok := c.tracer.accessList[string(path)]
|
||||||
if ok {
|
if ok {
|
||||||
c.nodes.AddNode(path, trienode.NewDeleted())
|
return n, &wrapNode{
|
||||||
|
path: string(path),
|
||||||
|
node: trienode.NewDeleted(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return n
|
return n, nil
|
||||||
}
|
}
|
||||||
// Collect the dirty node to nodeset for return.
|
|
||||||
nhash := common.BytesToHash(hash)
|
nhash := common.BytesToHash(hash)
|
||||||
c.nodes.AddNode(path, trienode.New(nhash, nodeToBytes(n)))
|
wNode := &wrapNode{
|
||||||
|
path: string(path),
|
||||||
|
node: trienode.New(nhash, nodeToBytes(n)),
|
||||||
|
}
|
||||||
|
|
||||||
// Collect the corresponding leaf node if it's required. We don't check
|
// Collect the corresponding leaf node if it's required. We don't check
|
||||||
// full node since it's impossible to store value in fullNode. The key
|
// full node since it's impossible to store value in fullNode. The key
|
||||||
// length of leaves should be exactly same.
|
// length of leaves should be exactly same..
|
||||||
if c.collectLeaf {
|
if c.collectLeaf {
|
||||||
if sn, ok := n.(*shortNode); ok {
|
if sn, ok := n.(*shortNode); ok {
|
||||||
if val, ok := sn.Val.(valueNode); ok {
|
if val, ok := sn.Val.(valueNode); ok {
|
||||||
c.nodes.AddLeaf(nhash, val)
|
c.nodes.AddLeaf(nhash, val)
|
||||||
|
wNode.leafHash = nhash
|
||||||
|
wNode.leafBlob = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return hash
|
|
||||||
|
return hash, wNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForGatherChildren decodes the provided node and traverses the children inside.
|
// ForGatherChildren decodes the provided node and traverses the children inside.
|
||||||
|
|
|
||||||
33
trie/trie.go
33
trie/trie.go
|
|
@ -44,16 +44,17 @@ type Trie struct {
|
||||||
// trie is not usable(latest states is invisible).
|
// trie is not usable(latest states is invisible).
|
||||||
committed bool
|
committed bool
|
||||||
|
|
||||||
// Keep track of the number leaves which have been inserted since the last
|
|
||||||
// hashing operation. This number will not directly map to the number of
|
|
||||||
// actually unhashed nodes.
|
|
||||||
unhashed int
|
|
||||||
|
|
||||||
// reader is the handler trie can retrieve nodes from.
|
// reader is the handler trie can retrieve nodes from.
|
||||||
reader *trieReader
|
reader *trieReader
|
||||||
|
|
||||||
// tracer is the tool to track the trie changes.
|
// tracer is the tool to track the trie changes.
|
||||||
tracer *tracer
|
tracer *tracer
|
||||||
|
|
||||||
|
// The number of trie mutations that have been performed
|
||||||
|
mutate int
|
||||||
|
|
||||||
|
// The number of mutations that have been hashed
|
||||||
|
hashed int
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFlag returns the cache flag value for a newly created node.
|
// newFlag returns the cache flag value for a newly created node.
|
||||||
|
|
@ -67,9 +68,10 @@ func (t *Trie) Copy() *Trie {
|
||||||
root: t.root,
|
root: t.root,
|
||||||
owner: t.owner,
|
owner: t.owner,
|
||||||
committed: t.committed,
|
committed: t.committed,
|
||||||
unhashed: t.unhashed,
|
|
||||||
reader: t.reader,
|
reader: t.reader,
|
||||||
tracer: t.tracer.copy(),
|
tracer: t.tracer.copy(),
|
||||||
|
mutate: t.mutate,
|
||||||
|
hashed: t.hashed,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,11 +306,12 @@ func (t *Trie) Update(key, value []byte) error {
|
||||||
if t.committed {
|
if t.committed {
|
||||||
return ErrCommitted
|
return ErrCommitted
|
||||||
}
|
}
|
||||||
|
t.mutate++
|
||||||
return t.update(key, value)
|
return t.update(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trie) update(key, value []byte) error {
|
func (t *Trie) update(key, value []byte) error {
|
||||||
t.unhashed++
|
t.mutate++
|
||||||
k := keybytesToHex(key)
|
k := keybytesToHex(key)
|
||||||
if len(value) != 0 {
|
if len(value) != 0 {
|
||||||
_, n, err := t.insert(t.root, nil, k, valueNode(value))
|
_, n, err := t.insert(t.root, nil, k, valueNode(value))
|
||||||
|
|
@ -422,7 +425,7 @@ func (t *Trie) Delete(key []byte) error {
|
||||||
if t.committed {
|
if t.committed {
|
||||||
return ErrCommitted
|
return ErrCommitted
|
||||||
}
|
}
|
||||||
t.unhashed++
|
t.mutate++
|
||||||
k := keybytesToHex(key)
|
k := keybytesToHex(key)
|
||||||
_, n, err := t.delete(t.root, nil, k)
|
_, n, err := t.delete(t.root, nil, k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -622,7 +625,7 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||||
}
|
}
|
||||||
nodes := trienode.NewNodeSet(t.owner)
|
nodes := trienode.NewNodeSet(t.owner)
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
nodes.AddNode([]byte(path), trienode.NewDeleted())
|
nodes.AddNode(path, trienode.NewDeleted())
|
||||||
}
|
}
|
||||||
return types.EmptyRootHash, nodes // case (b)
|
return types.EmptyRootHash, nodes // case (b)
|
||||||
}
|
}
|
||||||
|
|
@ -640,9 +643,10 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
|
||||||
}
|
}
|
||||||
nodes := trienode.NewNodeSet(t.owner)
|
nodes := trienode.NewNodeSet(t.owner)
|
||||||
for _, path := range t.tracer.deletedNodes() {
|
for _, path := range t.tracer.deletedNodes() {
|
||||||
nodes.AddNode([]byte(path), trienode.NewDeleted())
|
nodes.AddNode(path, trienode.NewDeleted())
|
||||||
}
|
}
|
||||||
t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root)
|
t.root = newCommitter(nodes, t.tracer, collectLeaf, t.mutate > 100).Commit(t.root)
|
||||||
|
t.mutate = 0
|
||||||
return rootHash, nodes
|
return rootHash, nodes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,10 +656,10 @@ func (t *Trie) hashRoot() (node, node) {
|
||||||
return hashNode(types.EmptyRootHash.Bytes()), nil
|
return hashNode(types.EmptyRootHash.Bytes()), nil
|
||||||
}
|
}
|
||||||
// If the number of changes is below 100, we let one thread handle it
|
// If the number of changes is below 100, we let one thread handle it
|
||||||
h := newHasher(t.unhashed >= 100)
|
h := newHasher(t.mutate-t.hashed >= 100)
|
||||||
defer func() {
|
defer func() {
|
||||||
returnHasherToPool(h)
|
returnHasherToPool(h)
|
||||||
t.unhashed = 0
|
t.hashed = t.mutate
|
||||||
}()
|
}()
|
||||||
hashed, cached := h.hash(t.root, true)
|
hashed, cached := h.hash(t.root, true)
|
||||||
return hashed, cached
|
return hashed, cached
|
||||||
|
|
@ -677,7 +681,8 @@ func (t *Trie) Witness() map[string]struct{} {
|
||||||
func (t *Trie) Reset() {
|
func (t *Trie) Reset() {
|
||||||
t.root = nil
|
t.root = nil
|
||||||
t.owner = common.Hash{}
|
t.owner = common.Hash{}
|
||||||
t.unhashed = 0
|
|
||||||
t.tracer.reset()
|
t.tracer.reset()
|
||||||
t.committed = false
|
t.committed = false
|
||||||
|
t.hashed = 0
|
||||||
|
t.mutate = 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,13 +90,13 @@ func (set *NodeSet) ForEachWithOrder(callback func(path string, n *Node)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddNode adds the provided node into set.
|
// AddNode adds the provided node into set.
|
||||||
func (set *NodeSet) AddNode(path []byte, n *Node) {
|
func (set *NodeSet) AddNode(path string, n *Node) {
|
||||||
if n.IsDeleted() {
|
if n.IsDeleted() {
|
||||||
set.deletes += 1
|
set.deletes += 1
|
||||||
} else {
|
} else {
|
||||||
set.updates += 1
|
set.updates += 1
|
||||||
}
|
}
|
||||||
set.Nodes[string(path)] = n
|
set.Nodes[path] = n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge adds a set of nodes into the set.
|
// Merge adds a set of nodes into the set.
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ func benchmarkMerge(b *testing.B, count int) {
|
||||||
blob := make([]byte, 32)
|
blob := make([]byte, 32)
|
||||||
rand.Read(blob)
|
rand.Read(blob)
|
||||||
hash := crypto.Keccak256Hash(blob)
|
hash := crypto.Keccak256Hash(blob)
|
||||||
s.AddNode(path, New(hash, blob))
|
s.AddNode(string(path), New(hash, blob))
|
||||||
}
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
// Random path of 4 nibbles
|
// Random path of 4 nibbles
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ func (t *VerkleTrie) Commit(_ bool) (common.Hash, *trienode.NodeSet) {
|
||||||
nodeset := trienode.NewNodeSet(common.Hash{})
|
nodeset := trienode.NewNodeSet(common.Hash{})
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
// Hash parameter is not used in pathdb
|
// Hash parameter is not used in pathdb
|
||||||
nodeset.AddNode(node.Path, trienode.New(common.Hash{}, node.SerializedBytes))
|
nodeset.AddNode(string(node.Path), trienode.New(common.Hash{}, node.SerializedBytes))
|
||||||
}
|
}
|
||||||
// Serialize root commitment form
|
// Serialize root commitment form
|
||||||
return t.Hash(), nodeset
|
return t.Hash(), nodeset
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue