@@ -23,6 +23,7 @@ import (
23
23
"math/big"
24
24
"slices"
25
25
"sort"
26
+ "sync"
26
27
"time"
27
28
28
29
"github.com/ethereum/go-ethereum/common"
@@ -37,6 +38,7 @@ import (
37
38
"github.com/ethereum/go-ethereum/trie/trienode"
38
39
"github.com/ethereum/go-ethereum/trie/triestate"
39
40
"github.com/holiman/uint256"
41
+ "golang.org/x/sync/errgroup"
40
42
)
41
43
42
44
type revision struct {
@@ -1146,66 +1148,108 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
1146
1148
storageTrieNodesUpdated int
1147
1149
storageTrieNodesDeleted int
1148
1150
nodes = trienode .NewMergedNodeSet ()
1149
- codeWriter = s .db .DiskDB ().NewBatch ()
1150
1151
)
1151
1152
// Handle all state deletions first
1152
1153
if err := s .handleDestruction (nodes ); err != nil {
1153
1154
return common.Hash {}, err
1154
1155
}
1155
- // Handle all state updates afterwards
1156
+ // Handle all state updates afterwards, concurrently to one another to shave
1157
+ // off some milliseconds from the commit operation. Also accumulate the code
1158
+ // writes to run in parallel with the computations.
1156
1159
start := time .Now ()
1160
+ var (
1161
+ code = s .db .DiskDB ().NewBatch ()
1162
+ lock sync.Mutex
1163
+ root common.Hash
1164
+ workers errgroup.Group
1165
+ )
1166
+ // Schedule the account trie first since that will be the biggest, so give
1167
+ // it the most time to crunch.
1168
+ //
1169
+ // TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain
1170
+ // heads, which seems excessive given that it doesn't do hashing, it just
1171
+ // shuffles some data. For comparison, the *hashing* at chain head is 2-3ms.
1172
+ // We need to investigate what's happening as it seems something's wonky.
1173
+ // Obviously it's not an end of the world issue, just something the original
1174
+ // code didn't anticipate for.
1175
+ workers .Go (func () error {
1176
+ // Write the account trie changes, measuring the amount of wasted time
1177
+ newroot , set , err := s .trie .Commit (true )
1178
+ if err != nil {
1179
+ return err
1180
+ }
1181
+ root = newroot
1182
+
1183
+ // Merge the dirty nodes of account trie into global set
1184
+ lock .Lock ()
1185
+ defer lock .Unlock ()
1186
+
1187
+ if set != nil {
1188
+ if err = nodes .Merge (set ); err != nil {
1189
+ return err
1190
+ }
1191
+ accountTrieNodesUpdated , accountTrieNodesDeleted = set .Size ()
1192
+ }
1193
+ s .AccountCommits = time .Since (start )
1194
+ return nil
1195
+ })
1196
+ // Schedule each of the storage tries that need to be updated, so they can
1197
+ // run concurrently to one another.
1198
+ //
1199
+ // TODO(karalabe): Experimentally, the account commit takes approximately the
1200
+ // same time as all the storage commits combined, so we could maybe only have
1201
+ // 2 threads in total. But that kind of depends on the account commit being
1202
+ // more expensive than it should be, so let's fix that and revisit this todo.
1157
1203
for addr , op := range s .mutations {
1158
1204
if op .isDelete () {
1159
1205
continue
1160
1206
}
1161
- obj := s .stateObjects [addr ]
1162
-
1163
1207
// Write any contract code associated with the state object
1208
+ obj := s .stateObjects [addr ]
1164
1209
if obj .code != nil && obj .dirtyCode {
1165
- rawdb .WriteCode (codeWriter , common .BytesToHash (obj .CodeHash ()), obj .code )
1210
+ rawdb .WriteCode (code , common .BytesToHash (obj .CodeHash ()), obj .code )
1166
1211
obj .dirtyCode = false
1167
1212
}
1168
- // Write any storage changes in the state object to its storage trie
1169
- set , err := obj .commit ()
1170
- if err != nil {
1171
- return common.Hash {}, err
1172
- }
1173
- // Merge the dirty nodes of storage trie into global set. It is possible
1174
- // that the account was destructed and then resurrected in the same block.
1175
- // In this case, the node set is shared by both accounts.
1176
- if set != nil {
1177
- if err := nodes .Merge (set ); err != nil {
1178
- return common.Hash {}, err
1213
+ // Run the storage updates concurrently to one another
1214
+ workers .Go (func () error {
1215
+ // Write any storage changes in the state object to its storage trie
1216
+ set , err := obj .commit ()
1217
+ if err != nil {
1218
+ return err
1179
1219
}
1180
- updates , deleted := set .Size ()
1181
- storageTrieNodesUpdated += updates
1182
- storageTrieNodesDeleted += deleted
1183
- }
1220
+ // Merge the dirty nodes of storage trie into global set. It is possible
1221
+ // that the account was destructed and then resurrected in the same block.
1222
+ // In this case, the node set is shared by both accounts.
1223
+ lock .Lock ()
1224
+ defer lock .Unlock ()
1225
+
1226
+ if set != nil {
1227
+ if err = nodes .Merge (set ); err != nil {
1228
+ return err
1229
+ }
1230
+ updates , deleted := set .Size ()
1231
+ storageTrieNodesUpdated += updates
1232
+ storageTrieNodesDeleted += deleted
1233
+ }
1234
+ s .StorageCommits = time .Since (start ) // overwrite with the longest storage commit runtime
1235
+ return nil
1236
+ })
1184
1237
}
1185
- s .StorageCommits += time .Since (start )
1186
-
1187
- if codeWriter .ValueSize () > 0 {
1188
- if err := codeWriter .Write (); err != nil {
1189
- log .Crit ("Failed to commit dirty codes" , "error" , err )
1238
+ // Schedule the code commits to run concurrently too. This shouldn't really
1239
+ // take much since we don't often commit code, but since it's disk access,
1240
+ // it's always yolo.
1241
+ workers .Go (func () error {
1242
+ if code .ValueSize () > 0 {
1243
+ if err := code .Write (); err != nil {
1244
+ log .Crit ("Failed to commit dirty codes" , "error" , err )
1245
+ }
1190
1246
}
1191
- }
1192
- // Write the account trie changes, measuring the amount of wasted time
1193
- start = time .Now ()
1194
-
1195
- root , set , err := s .trie .Commit (true )
1196
- if err != nil {
1247
+ return nil
1248
+ })
1249
+ // Wait for everything to finish and update the metrics
1250
+ if err := workers .Wait (); err != nil {
1197
1251
return common.Hash {}, err
1198
1252
}
1199
- // Merge the dirty nodes of account trie into global set
1200
- if set != nil {
1201
- if err := nodes .Merge (set ); err != nil {
1202
- return common.Hash {}, err
1203
- }
1204
- accountTrieNodesUpdated , accountTrieNodesDeleted = set .Size ()
1205
- }
1206
- // Report the commit metrics
1207
- s .AccountCommits += time .Since (start )
1208
-
1209
1253
accountUpdatedMeter .Mark (int64 (s .AccountUpdated ))
1210
1254
storageUpdatedMeter .Mark (int64 (s .StorageUpdated ))
1211
1255
accountDeletedMeter .Mark (int64 (s .AccountDeleted ))
0 commit comments