@@ -23,6 +23,7 @@ import (
2323 "math/big"
2424 "slices"
2525 "sort"
26+ "sync"
2627 "time"
2728
2829 "github.com/ethereum/go-ethereum/common"
@@ -37,6 +38,7 @@ import (
3738 "github.com/ethereum/go-ethereum/trie/trienode"
3839 "github.com/ethereum/go-ethereum/trie/triestate"
3940 "github.com/holiman/uint256"
41+ "golang.org/x/sync/errgroup"
4042)
4143
4244type revision struct {
@@ -1146,66 +1148,108 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
11461148 storageTrieNodesUpdated int
11471149 storageTrieNodesDeleted int
11481150 nodes = trienode .NewMergedNodeSet ()
1149- codeWriter = s .db .DiskDB ().NewBatch ()
11501151 )
11511152 // Handle all state deletions first
11521153 if err := s .handleDestruction (nodes ); err != nil {
11531154 return common.Hash {}, err
11541155 }
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.
11561159 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.
11571203 for addr , op := range s .mutations {
11581204 if op .isDelete () {
11591205 continue
11601206 }
1161- obj := s .stateObjects [addr ]
1162-
11631207 // Write any contract code associated with the state object
1208+ obj := s .stateObjects [addr ]
11641209 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 )
11661211 obj .dirtyCode = false
11671212 }
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
11791219 }
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+ })
11841237 }
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+ }
11901246 }
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 {
11971251 return common.Hash {}, err
11981252 }
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-
12091253 accountUpdatedMeter .Mark (int64 (s .AccountUpdated ))
12101254 storageUpdatedMeter .Mark (int64 (s .StorageUpdated ))
12111255 accountDeletedMeter .Mark (int64 (s .AccountDeleted ))
0 commit comments