Skip to content

Commit 118ed44

Browse files
holimanstevemilkrjl493456442
committed
trie: concurrent commit (#30545)
This change makes the trie commit operation concurrent, if the number of changes exceed 100. Co-authored-by: stevemilk <wangpeculiar@gmail.com> Co-authored-by: Gary Rong <garyrong0905@gmail.com>
1 parent 912a137 commit 118ed44

File tree

4 files changed

+168
-15
lines changed

4 files changed

+168
-15
lines changed

trie/committer.go

+30-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package trie
1818

1919
import (
2020
"fmt"
21+
"sync"
2122

2223
"github.com/ethereum/go-ethereum/common"
2324
"github.com/ethereum/go-ethereum/trie/trienode"
@@ -42,12 +43,12 @@ func newCommitter(nodeset *trienode.NodeSet, tracer *tracer, collectLeaf bool) *
4243
}
4344

4445
// Commit collapses a node down into a hash node.
45-
func (c *committer) Commit(n node) hashNode {
46-
return c.commit(nil, n).(hashNode)
46+
func (c *committer) Commit(n node, parallel bool) hashNode {
47+
return c.commit(nil, n, parallel).(hashNode)
4748
}
4849

4950
// commit collapses a node down into a hash node and returns it.
50-
func (c *committer) commit(path []byte, n node) node {
51+
func (c *committer) commit(path []byte, n node, parallel bool) node {
5152
// if this path is clean, use available cached data
5253
hash, dirty := n.cache()
5354
if hash != nil && !dirty {
@@ -62,7 +63,7 @@ func (c *committer) commit(path []byte, n node) node {
6263
// If the child is fullNode, recursively commit,
6364
// otherwise it can only be hashNode or valueNode.
6465
if _, ok := cn.Val.(*fullNode); ok {
65-
collapsed.Val = c.commit(append(path, cn.Key...), cn.Val)
66+
collapsed.Val = c.commit(append(path, cn.Key...), cn.Val, false)
6667
}
6768
// The key needs to be copied, since we're adding it to the
6869
// modified nodeset.
@@ -73,7 +74,7 @@ func (c *committer) commit(path []byte, n node) node {
7374
}
7475
return collapsed
7576
case *fullNode:
76-
hashedKids := c.commitChildren(path, cn)
77+
hashedKids := c.commitChildren(path, cn, parallel)
7778
collapsed := cn.copy()
7879
collapsed.Children = hashedKids
7980

@@ -91,8 +92,12 @@ func (c *committer) commit(path []byte, n node) node {
9192
}
9293

9394
// commitChildren commits the children of the given fullnode
94-
func (c *committer) commitChildren(path []byte, n *fullNode) [17]node {
95-
var children [17]node
95+
func (c *committer) commitChildren(path []byte, n *fullNode, parallel bool) [17]node {
96+
var (
97+
wg sync.WaitGroup
98+
nodesMu sync.Mutex
99+
children [17]node
100+
)
96101
for i := 0; i < 16; i++ {
97102
child := n.Children[i]
98103
if child == nil {
@@ -108,7 +113,24 @@ func (c *committer) commitChildren(path []byte, n *fullNode) [17]node {
108113
// Commit the child recursively and store the "hashed" value.
109114
// Note the returned node can be some embedded nodes, so it's
110115
// possible the type is not hashNode.
111-
children[i] = c.commit(append(path, byte(i)), child)
116+
if !parallel {
117+
children[i] = c.commit(append(path, byte(i)), child, false)
118+
} else {
119+
wg.Add(1)
120+
go func(index int) {
121+
p := append(path, byte(index))
122+
childSet := trienode.NewNodeSet(c.nodes.Owner)
123+
childCommitter := newCommitter(childSet, c.tracer, c.collectLeaf)
124+
children[index] = childCommitter.commit(p, child, false)
125+
nodesMu.Lock()
126+
c.nodes.MergeSet(childSet)
127+
nodesMu.Unlock()
128+
wg.Done()
129+
}(i)
130+
}
131+
}
132+
if parallel {
133+
wg.Wait()
112134
}
113135
// For the 17th child, it's possible the type is valuenode.
114136
if n.Children[16] != nil {

trie/trie.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ type Trie struct {
4949
// actually unhashed nodes.
5050
unhashed int
5151

52+
// uncommitted is the number of updates since last commit.
53+
uncommitted int
54+
5255
// reader is the handler trie can retrieve nodes from.
5356
reader *trieReader
5457

@@ -64,12 +67,13 @@ func (t *Trie) newFlag() nodeFlag {
6467
// Copy returns a copy of Trie.
6568
func (t *Trie) Copy() *Trie {
6669
return &Trie{
67-
root: t.root,
68-
owner: t.owner,
69-
committed: t.committed,
70-
unhashed: t.unhashed,
71-
reader: t.reader,
72-
tracer: t.tracer.copy(),
70+
root: t.root,
71+
owner: t.owner,
72+
committed: t.committed,
73+
reader: t.reader,
74+
tracer: t.tracer.copy(),
75+
uncommitted: t.uncommitted,
76+
unhashed: t.unhashed,
7377
}
7478
}
7579

@@ -309,6 +313,7 @@ func (t *Trie) Update(key, value []byte) error {
309313

310314
func (t *Trie) update(key, value []byte) error {
311315
t.unhashed++
316+
t.uncommitted++
312317
k := keybytesToHex(key)
313318
if len(value) != 0 {
314319
_, n, err := t.insert(t.root, nil, k, valueNode(value))
@@ -422,6 +427,7 @@ func (t *Trie) Delete(key []byte) error {
422427
if t.committed {
423428
return ErrCommitted
424429
}
430+
t.uncommitted++
425431
t.unhashed++
426432
k := keybytesToHex(key)
427433
_, n, err := t.delete(t.root, nil, k)
@@ -642,7 +648,9 @@ func (t *Trie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
642648
for _, path := range t.tracer.deletedNodes() {
643649
nodes.AddNode([]byte(path), trienode.NewDeleted())
644650
}
645-
t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root)
651+
// If the number of changes is below 100, we let one thread handle it
652+
t.root = newCommitter(nodes, t.tracer, collectLeaf).Commit(t.root, t.uncommitted > 100)
653+
t.uncommitted = 0
646654
return rootHash, nodes
647655
}
648656

@@ -678,6 +686,7 @@ func (t *Trie) Reset() {
678686
t.root = nil
679687
t.owner = common.Hash{}
680688
t.unhashed = 0
689+
t.uncommitted = 0
681690
t.tracer.reset()
682691
t.committed = false
683692
}

trie/trie_test.go

+104
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"math/rand"
2727
"reflect"
2828
"sort"
29+
"strings"
2930
"testing"
3031
"testing/quick"
3132

@@ -35,6 +36,7 @@ import (
3536
"github.com/ethereum/go-ethereum/core/types"
3637
"github.com/ethereum/go-ethereum/crypto"
3738
"github.com/ethereum/go-ethereum/ethdb"
39+
"github.com/ethereum/go-ethereum/internal/testrand"
3840
"github.com/ethereum/go-ethereum/rlp"
3941
"github.com/ethereum/go-ethereum/trie/trienode"
4042
"github.com/holiman/uint256"
@@ -1206,3 +1208,105 @@ func FuzzTrie(f *testing.F) {
12061208
}
12071209
})
12081210
}
1211+
1212+
func BenchmarkCommit(b *testing.B) {
1213+
benchmarkCommit(b, 100)
1214+
benchmarkCommit(b, 500)
1215+
benchmarkCommit(b, 2000)
1216+
benchmarkCommit(b, 5000)
1217+
}
1218+
1219+
func benchmarkCommit(b *testing.B, n int) {
1220+
b.Run(fmt.Sprintf("commit-%vnodes-sequential", n), func(b *testing.B) {
1221+
testCommit(b, n, false)
1222+
})
1223+
b.Run(fmt.Sprintf("commit-%vnodes-parallel", n), func(b *testing.B) {
1224+
testCommit(b, n, true)
1225+
})
1226+
}
1227+
1228+
func testCommit(b *testing.B, n int, parallel bool) {
1229+
tries := make([]*Trie, b.N)
1230+
for i := 0; i < b.N; i++ {
1231+
tries[i] = NewEmpty(nil)
1232+
for j := 0; j < n; j++ {
1233+
key := testrand.Bytes(32)
1234+
val := testrand.Bytes(32)
1235+
tries[i].Update(key, val)
1236+
}
1237+
tries[i].Hash()
1238+
if !parallel {
1239+
tries[i].uncommitted = 0
1240+
}
1241+
}
1242+
b.ResetTimer()
1243+
b.ReportAllocs()
1244+
for i := 0; i < len(tries); i++ {
1245+
tries[i].Commit(true)
1246+
}
1247+
}
1248+
1249+
func TestCommitCorrect(t *testing.T) {
1250+
var paraTrie = NewEmpty(nil)
1251+
var refTrie = NewEmpty(nil)
1252+
1253+
for j := 0; j < 5000; j++ {
1254+
key := testrand.Bytes(32)
1255+
val := testrand.Bytes(32)
1256+
paraTrie.Update(key, val)
1257+
refTrie.Update(common.CopyBytes(key), common.CopyBytes(val))
1258+
}
1259+
paraTrie.Hash()
1260+
refTrie.Hash()
1261+
refTrie.uncommitted = 0
1262+
1263+
haveRoot, haveNodes := paraTrie.Commit(true)
1264+
wantRoot, wantNodes := refTrie.Commit(true)
1265+
1266+
if haveRoot != wantRoot {
1267+
t.Fatalf("have %x want %x", haveRoot, wantRoot)
1268+
}
1269+
have := printSet(haveNodes)
1270+
want := printSet(wantNodes)
1271+
if have != want {
1272+
i := 0
1273+
for i = 0; i < len(have); i++ {
1274+
if have[i] != want[i] {
1275+
break
1276+
}
1277+
}
1278+
if i > 100 {
1279+
i -= 100
1280+
}
1281+
t.Fatalf("have != want\nhave %q\nwant %q", have[i:], want[i:])
1282+
}
1283+
}
1284+
func printSet(set *trienode.NodeSet) string {
1285+
var out = new(strings.Builder)
1286+
fmt.Fprintf(out, "nodeset owner: %v\n", set.Owner)
1287+
var paths []string
1288+
for k := range set.Nodes {
1289+
paths = append(paths, k)
1290+
}
1291+
sort.Strings(paths)
1292+
1293+
for _, path := range paths {
1294+
n := set.Nodes[path]
1295+
// Deletion
1296+
if n.IsDeleted() {
1297+
fmt.Fprintf(out, " [-]: %x\n", path)
1298+
continue
1299+
}
1300+
// Insertion or update
1301+
fmt.Fprintf(out, " [+/*]: %x -> %v \n", path, n.Hash)
1302+
}
1303+
sort.Slice(set.Leaves, func(i, j int) bool {
1304+
a := set.Leaves[i]
1305+
b := set.Leaves[j]
1306+
return bytes.Compare(a.Parent[:], b.Parent[:]) < 0
1307+
})
1308+
for _, n := range set.Leaves {
1309+
fmt.Fprintf(out, "[leaf]: %v\n", n)
1310+
}
1311+
return out.String()
1312+
}

trie/trienode/node.go

+18
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package trienode
1818

1919
import (
2020
"fmt"
21+
"maps"
2122
"sort"
2223
"strings"
2324

@@ -99,6 +100,23 @@ func (set *NodeSet) AddNode(path []byte, n *Node) {
99100
set.Nodes[string(path)] = n
100101
}
101102

103+
// MergeSet merges this 'set' with 'other'. It assumes that the sets are disjoint,
104+
// and thus does not deduplicate data (count deletes, dedup leaves etc).
105+
func (set *NodeSet) MergeSet(other *NodeSet) error {
106+
if set.Owner != other.Owner {
107+
return fmt.Errorf("nodesets belong to different owner are not mergeable %x-%x", set.Owner, other.Owner)
108+
}
109+
maps.Copy(set.Nodes, other.Nodes)
110+
111+
set.deletes += other.deletes
112+
set.updates += other.updates
113+
114+
// Since we assume the sets are disjoint, we can safely append leaves
115+
// like this without deduplication.
116+
set.Leaves = append(set.Leaves, other.Leaves...)
117+
return nil
118+
}
119+
102120
// Merge adds a set of nodes into the set.
103121
func (set *NodeSet) Merge(owner common.Hash, nodes map[string]*Node) error {
104122
if set.Owner != owner {

0 commit comments

Comments
 (0)