core/state: convert prefetcher to concurrent per-trie loader
This commit is contained in:
@ -63,7 +63,7 @@ func (n *proofList) Delete(key []byte) error {
|
||||
// * Accounts
|
||||
type StateDB struct {
|
||||
db Database
|
||||
prefetcher *TriePrefetcher
|
||||
prefetcher *triePrefetcher
|
||||
originalRoot common.Hash // The pre-state root, before any changes were made
|
||||
trie Trie
|
||||
hasher crypto.KeccakState
|
||||
@ -149,10 +149,25 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
|
||||
return sdb, nil
|
||||
}
|
||||
|
||||
func (s *StateDB) UsePrefetcher(prefetcher *TriePrefetcher) {
|
||||
if prefetcher != nil {
|
||||
s.prefetcher = prefetcher
|
||||
s.prefetcher.Resume(s.originalRoot)
|
||||
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
|
||||
// state trie concurrently while the state is mutated so that when we reach the
|
||||
// commit phase, most of the needed data is already hot.
|
||||
func (s *StateDB) StartPrefetcher(namespace string) {
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}
|
||||
if s.snap != nil {
|
||||
s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
// StopPrefetcher terminates a running prefetcher and reports any leftover stats
|
||||
// from the gathered metrics.
|
||||
func (s *StateDB) StopPrefetcher() {
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,37 +182,6 @@ func (s *StateDB) Error() error {
|
||||
return s.dbErr
|
||||
}
|
||||
|
||||
// Reset clears out all ephemeral state objects from the state db, but keeps
|
||||
// the underlying state trie to avoid reloading data for the next operations.
|
||||
func (s *StateDB) Reset(root common.Hash) error {
|
||||
tr, err := s.db.OpenTrie(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.trie = tr
|
||||
s.stateObjects = make(map[common.Address]*stateObject)
|
||||
s.stateObjectsPending = make(map[common.Address]struct{})
|
||||
s.stateObjectsDirty = make(map[common.Address]struct{})
|
||||
s.thash = common.Hash{}
|
||||
s.bhash = common.Hash{}
|
||||
s.txIndex = 0
|
||||
s.logs = make(map[common.Hash][]*types.Log)
|
||||
s.logSize = 0
|
||||
s.preimages = make(map[common.Hash][]byte)
|
||||
s.clearJournalAndRefund()
|
||||
|
||||
if s.snaps != nil {
|
||||
s.snapAccounts, s.snapDestructs, s.snapStorage = nil, nil, nil
|
||||
if s.snap = s.snaps.Snapshot(root); s.snap != nil {
|
||||
s.snapDestructs = make(map[common.Hash]struct{})
|
||||
s.snapAccounts = make(map[common.Hash][]byte)
|
||||
s.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
|
||||
}
|
||||
}
|
||||
s.accessList = newAccessList()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StateDB) AddLog(log *types.Log) {
|
||||
s.journal.append(addLogChange{txhash: s.thash})
|
||||
|
||||
@ -737,6 +721,13 @@ func (s *StateDB) Copy() *StateDB {
|
||||
// However, it doesn't cost us much to copy an empty list, so we do it anyway
|
||||
// to not blow up if we ever decide copy it in the middle of a transaction
|
||||
state.accessList = s.accessList.Copy()
|
||||
|
||||
// If there's a prefetcher running, make an inactive copy of it that can
|
||||
// only access data but does not actively preload (since the user will not
|
||||
// know that they need to explicitly terminate an active copy).
|
||||
if s.prefetcher != nil {
|
||||
state.prefetcher = s.prefetcher.copy()
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
@ -773,7 +764,7 @@ func (s *StateDB) GetRefund() uint64 {
|
||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
var addressesToPrefetch []common.Address
|
||||
addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties))
|
||||
for addr := range s.journal.dirties {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
@ -798,21 +789,19 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
|
||||
delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect)
|
||||
}
|
||||
} else {
|
||||
obj.finalise()
|
||||
obj.finalise(true) // Prefetch slots in the background
|
||||
}
|
||||
s.stateObjectsPending[addr] = struct{}{}
|
||||
s.stateObjectsDirty[addr] = struct{}{}
|
||||
|
||||
// At this point, also ship the address off to the precacher. The precacher
|
||||
// will start loading tries, and when the change is eventually committed,
|
||||
// the commit-phase will be a lot faster
|
||||
if s.prefetcher != nil {
|
||||
addressesToPrefetch = append(addressesToPrefetch, addr)
|
||||
}
|
||||
addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||
}
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.PrefetchAddresses(addressesToPrefetch)
|
||||
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
|
||||
s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch)
|
||||
}
|
||||
|
||||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
}
|
||||
@ -824,29 +813,49 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||
// Finalise all the dirty storage states and write them into the tries
|
||||
s.Finalise(deleteEmptyObjects)
|
||||
|
||||
// Now we're about to start to write changes to the trie. The trie is so
|
||||
// far _untouched_. We can check with the prefetcher, if it can give us
|
||||
// a trie which has the same root, but also has some content loaded into it.
|
||||
// If so, use that one instead.
|
||||
// If there was a trie prefetcher operating, it gets aborted and irrevocably
|
||||
// modified after we start retrieving tries. Remove it from the statedb after
|
||||
// this round of use.
|
||||
//
|
||||
// This is weird pre-byzantium since the first tx runs with a prefetcher and
|
||||
// the remainder without, but pre-byzantium even the initial prefetcher is
|
||||
// useless, so no sleep lost.
|
||||
prefetcher := s.prefetcher
|
||||
if s.prefetcher != nil {
|
||||
s.prefetcher.Pause()
|
||||
// We only want to do this _once_, if someone calls IntermediateRoot again,
|
||||
// we shouldn't fetch the trie again
|
||||
if s.originalRoot != (common.Hash{}) {
|
||||
if trie := s.prefetcher.GetTrie(s.originalRoot); trie != nil {
|
||||
s.trie = trie
|
||||
}
|
||||
s.originalRoot = common.Hash{}
|
||||
defer func() {
|
||||
s.prefetcher.close()
|
||||
s.prefetcher = nil
|
||||
}()
|
||||
}
|
||||
// Although naively it makes sense to retrieve the account trie and then do
|
||||
// the contract storage and account updates sequentially, that short circuits
|
||||
// the account prefetcher. Instead, let's process all the storage updates
|
||||
// first, giving the account prefeches just a few more milliseconds of time
|
||||
// to pull useful data from disk.
|
||||
for addr := range s.stateObjectsPending {
|
||||
if obj := s.stateObjects[addr]; !obj.deleted {
|
||||
obj.updateRoot(s.db)
|
||||
}
|
||||
}
|
||||
// Now we're about to start to write changes to the trie. The trie is so far
|
||||
// _untouched_. We can check with the prefetcher, if it can give us a trie
|
||||
// which has the same root, but also has some content loaded into it.
|
||||
if prefetcher != nil {
|
||||
if trie := prefetcher.trie(s.originalRoot); trie != nil {
|
||||
s.trie = trie
|
||||
}
|
||||
}
|
||||
usedAddrs := make([][]byte, 0, len(s.stateObjectsPending))
|
||||
for addr := range s.stateObjectsPending {
|
||||
obj := s.stateObjects[addr]
|
||||
if obj.deleted {
|
||||
if obj := s.stateObjects[addr]; obj.deleted {
|
||||
s.deleteStateObject(obj)
|
||||
} else {
|
||||
obj.updateRoot(s.db)
|
||||
s.updateStateObject(obj)
|
||||
}
|
||||
usedAddrs = append(usedAddrs, common.CopyBytes(addr[:])) // Copy needed for closure
|
||||
}
|
||||
if prefetcher != nil {
|
||||
prefetcher.used(s.originalRoot, usedAddrs)
|
||||
}
|
||||
if len(s.stateObjectsPending) > 0 {
|
||||
s.stateObjectsPending = make(map[common.Address]struct{})
|
||||
|
Reference in New Issue
Block a user