From a86220dfc367a43425faa0d0d3b0ba6a36aafe05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 8 Aug 2019 10:08:20 +0300 Subject: [PATCH] core/state: don't sort snapshot ordering --- core/blockchain.go | 45 ++++++++++++----------- core/state/snapshot/difflayer.go | 55 ++++++++++++++++------------ core/state/snapshot/snapshot.go | 4 +-- core/state/state_object.go | 24 ++++++------- core/state/statedb.go | 62 ++++++++++++++++++-------------- core/state_prefetcher.go | 2 ++ 6 files changed, 108 insertions(+), 84 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index f39b288724..ff630cfbdd 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -51,17 +51,19 @@ var ( headHeaderGauge = metrics.NewRegisteredGauge("chain/head/header", nil) headFastBlockGauge = metrics.NewRegisteredGauge("chain/head/receipt", nil) - accountSnapReadTimer = metrics.NewRegisteredTimer("chain/account/snapreads", nil) - accountTrieReadTimer = metrics.NewRegisteredTimer("chain/account/triereads", nil) - accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil) - accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil) - accountCommitTimer = metrics.NewRegisteredTimer("chain/account/commits", nil) + accountReadTimer = metrics.NewRegisteredTimer("chain/account/reads", nil) + accountHashTimer = metrics.NewRegisteredTimer("chain/account/hashes", nil) + accountUpdateTimer = metrics.NewRegisteredTimer("chain/account/updates", nil) + accountCommitTimer = metrics.NewRegisteredTimer("chain/account/commits", nil) - storageSnapReadTimer = metrics.NewRegisteredTimer("chain/storage/snapreads", nil) - storageTrieReadTimer = metrics.NewRegisteredTimer("chain/storage/triereads", nil) - storageHashTimer = metrics.NewRegisteredTimer("chain/storage/hashes", nil) - storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) - storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) + storageReadTimer = metrics.NewRegisteredTimer("chain/storage/reads", nil) + storageHashTimer = metrics.NewRegisteredTimer("chain/storage/hashes", nil) + storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) + storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) + + snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/accountreads", nil) + snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storagereads", nil) + snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil) @@ -1630,16 +1632,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] return it.index, events, coalescedLogs, err } // Update the metrics touched during block processing - accountSnapReadTimer.Update(statedb.AccountSnapReads) // Account reads are complete, we can mark them - accountTrieReadTimer.Update(statedb.AccountTrieReads) // Account reads are complete, we can mark them - storageSnapReadTimer.Update(statedb.StorageSnapReads) // Storage reads are complete, we can mark them - storageTrieReadTimer.Update(statedb.StorageTrieReads) // Storage reads are complete, we can mark them - accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them - storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them + accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them + storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them + accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them + storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them + snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them + snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation - trieproc := statedb.AccountSnapReads + statedb.AccountTrieReads + statedb.AccountUpdates - trieproc += statedb.StorageSnapReads + statedb.StorageTrieReads + statedb.StorageUpdates + trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates + trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates blockExecutionTimer.Update(time.Since(substart) - trieproc - triehash) @@ -1668,10 +1670,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] atomic.StoreUint32(&followupInterrupt, 1) // Update the metrics touched during block commit - accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them - storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them - blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits) + blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits) blockInsertTimer.UpdateSince(start) switch status { diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index e93d9fbf70..5ffc4047d9 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -43,10 +43,12 @@ type diffLayer struct { number uint64 // Block number to which this snapshot diff belongs to root common.Hash // Root hash to which this snapshot diff belongs to - accountOrder []common.Hash // Sorted accounts for iterated retrieval - accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) - storageOrder map[common.Hash][]common.Hash // Sorted storage slots for iterated retrievals. one per account - storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) + accountList []common.Hash // List of account for iteration, might not be sorted yet (lazy) + accountSorted bool // Flag whether the account list has alreayd been sorted or not + accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) + storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account + storageSorted map[common.Hash]bool // Flag whether the storage slot list has alreayd been sorted or not + storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) lock sync.RWMutex } @@ -65,16 +67,21 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] storageData: storage, } // Fill the account hashes and sort them for the iterator - dl.accountOrder = make([]common.Hash, 0, len(accounts)) + accountList := make([]common.Hash, 0, len(accounts)) for hash, data := range accounts { - dl.accountOrder = append(dl.accountOrder, hash) + accountList = append(accountList, hash) dl.memory += uint64(len(data)) } - sort.Sort(hashes(dl.accountOrder)) - dl.memory += uint64(len(dl.accountOrder) * common.HashLength) + sort.Sort(hashes(accountList)) + dl.accountList = accountList + dl.accountSorted = true + + dl.memory += uint64(len(dl.accountList) * common.HashLength) // Fill the storage hashes and sort them for the iterator - dl.storageOrder = make(map[common.Hash][]common.Hash, len(storage)) + dl.storageList = make(map[common.Hash][]common.Hash, len(storage)) + dl.storageSorted = make(map[common.Hash]bool, len(storage)) + for accountHash, slots := range storage { // If the slots are nil, sanity check that it's a deleted account if slots == nil { @@ -83,7 +90,7 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] panic(fmt.Sprintf("storage in %#x nil, but account conflicts (%#x, exists: %v)", accountHash, account, ok)) } // Everything ok, store the deletion mark and continue - dl.storageOrder[accountHash] = nil + dl.storageList[accountHash] = nil continue } // Storage slots are not nil so entire contract was not deleted, ensure the @@ -93,16 +100,18 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] //panic(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) } // Fill the storage hashes for this account and sort them for the iterator - storageOrder := make([]common.Hash, 0, len(slots)) + storageList := make([]common.Hash, 0, len(slots)) for storageHash, data := range slots { - storageOrder = append(storageOrder, storageHash) + storageList = append(storageList, storageHash) dl.memory += uint64(len(data)) } - sort.Sort(hashes(storageOrder)) - dl.storageOrder[accountHash] = storageOrder - dl.memory += uint64(len(storageOrder) * common.HashLength) + sort.Sort(hashes(storageList)) + dl.storageList[accountHash] = storageList + dl.storageSorted[accountHash] = true + + dl.memory += uint64(len(storageList) * common.HashLength) } - dl.memory += uint64(len(dl.storageOrder) * common.HashLength) + dl.memory += uint64(len(dl.storageList) * common.HashLength) return dl } @@ -206,9 +215,6 @@ func (dl *diffLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]by // the layer limit is reached, memory cap is also enforced (but not before). The // block numbers for the disk layer and first diff layer are returned for GC. func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { - dl.lock.Lock() - defer dl.lock.Unlock() - // Dive until we run out of layers or reach the persistent database if layers > 2 { // If we still have diff layers below, recurse @@ -224,6 +230,9 @@ func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { case *diskLayer: return parent.number, dl.number case *diffLayer: + dl.lock.Lock() + defer dl.lock.Unlock() + dl.parent = parent.flatten() if dl.parent.(*diffLayer).memory < memory { diskNumber, _ := parent.parent.Info() @@ -294,14 +303,15 @@ func (dl *diffLayer) flatten() snapshot { for hash, data := range dl.accountData { parent.accountData[hash] = data } - parent.accountOrder = merge(parent.accountOrder, dl.accountOrder) + parent.accountList = append(parent.accountList, dl.accountList...) // TODO(karalabe): dedup!! + parent.accountSorted = false // Overwrite all the updates storage slots (individually) for accountHash, storage := range dl.storageData { // If storage didn't exist (or was deleted) in the parent; or if the storage // was freshly deleted in the child, overwrite blindly if parent.storageData[accountHash] == nil || storage == nil { - parent.storageOrder[accountHash] = dl.storageOrder[accountHash] + parent.storageList[accountHash] = dl.storageList[accountHash] parent.storageData[accountHash] = storage continue } @@ -310,8 +320,9 @@ func (dl *diffLayer) flatten() snapshot { for storageHash, data := range storage { comboData[storageHash] = data } - parent.storageOrder[accountHash] = merge(parent.storageOrder[accountHash], dl.storageOrder[accountHash]) parent.storageData[accountHash] = comboData + parent.storageList[accountHash] = append(parent.storageList[accountHash], dl.storageList[accountHash]...) // TODO(karalabe): dedup!! + parent.storageSorted[accountHash] = false } // Return the combo parent parent.number = dl.number diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index a3f5d8037b..e7a029ffaa 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -158,9 +158,9 @@ func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) er defer st.lock.Unlock() diskNumber, diffNumber := snap.Cap(layers, memory) - for hash, snap := range st.layers { + for root, snap := range st.layers { if number, _ := snap.Info(); number != diskNumber && number < diffNumber { - delete(st.layers, hash) + delete(st.layers, root) } } return nil diff --git a/core/state/state_object.go b/core/state/state_object.go index 167bc5bcbf..9706797c95 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -193,22 +193,22 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has enc []byte err error ) - /* if s.db.snap != nil { + if s.db.snap != nil { if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageSnapReads += time.Since(start) }(time.Now()) + defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now()) } enc = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:])) - } else {*/ - // Track the amount of time wasted on reading the storage trie - if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageTrieReads += time.Since(start) }(time.Now()) + } else { + // Track the amount of time wasted on reading the storage trie + if metrics.EnabledExpensive { + defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) + } + // Otherwise load the value from the database + if enc, err = s.getTrie(db).TryGet(key[:]); err != nil { + s.setError(err) + return common.Hash{} + } } - // Otherwise load the value from the database - if enc, err = s.getTrie(db).TryGet(key[:]); err != nil { - s.setError(err) - return common.Hash{} - } - //} if len(enc) > 0 { _, content, _, err := rlp.Split(enc) if err != nil { diff --git a/core/state/statedb.go b/core/state/statedb.go index 4613f6836f..e817c14dc7 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -100,16 +100,17 @@ type StateDB struct { nextRevisionId int // Measurements gathered during execution for debugging purposes - AccountSnapReads time.Duration - AccountTrieReads time.Duration - AccountHashes time.Duration - AccountUpdates time.Duration - AccountCommits time.Duration - StorageSnapReads time.Duration - StorageTrieReads time.Duration - StorageHashes time.Duration - StorageUpdates time.Duration - StorageCommits time.Duration + AccountReads time.Duration + AccountHashes time.Duration + AccountUpdates time.Duration + AccountCommits time.Duration + StorageReads time.Duration + StorageHashes time.Duration + StorageUpdates time.Duration + StorageCommits time.Duration + SnapshotAccountReads time.Duration + SnapshotStorageReads time.Duration + SnapshotCommits time.Duration } // Create a new state from a given trie. @@ -494,9 +495,9 @@ func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) } // If no live objects are available, attempt to use snapshots var data Account - /*if s.snap != nil { + if s.snap != nil { if metrics.EnabledExpensive { - defer func(start time.Time) { s.AccountSnapReads += time.Since(start) }(time.Now()) + defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) } acc := s.snap.Account(crypto.Keccak256Hash(addr[:])) if acc == nil { @@ -510,21 +511,21 @@ func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) if data.Root == (common.Hash{}) { data.Root = emptyRoot } - } else {*/ - // Snapshot unavailable, fall back to the trie - if metrics.EnabledExpensive { - defer func(start time.Time) { s.AccountTrieReads += time.Since(start) }(time.Now()) + } else { + // Snapshot unavailable, fall back to the trie + if metrics.EnabledExpensive { + defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now()) + } + enc, err := s.trie.TryGet(addr[:]) + if len(enc) == 0 { + s.setError(err) + return nil + } + if err := rlp.DecodeBytes(enc, &data); err != nil { + log.Error("Failed to decode state object", "addr", addr, "err", err) + return nil + } } - enc, err := s.trie.TryGet(addr[:]) - if len(enc) == 0 { - s.setError(err) - return nil - } - if err := rlp.DecodeBytes(enc, &data); err != nil { - log.Error("Failed to decode state object", "addr", addr, "err", err) - return nil - } - //} // Insert into the live set obj := newObject(s, addr, data) s.setStateObject(obj) @@ -768,8 +769,9 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) delete(s.stateObjectsDirty, addr) } // Write the account trie changes, measuing the amount of wasted time + var start time.Time if metrics.EnabledExpensive { - defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now()) + start = time.Now() } root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error { var account Account @@ -785,8 +787,14 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) } return nil }) + if metrics.EnabledExpensive { + s.AccountCommits += time.Since(start) + } // If snapshotting is enabled, update the snapshot tree with this new version if s.snap != nil { + if metrics.EnabledExpensive { + defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now()) + } _, parentRoot := s.snap.Info() if err := s.snaps.Update(root, parentRoot, s.snapAccounts, s.snapStorage); err != nil { log.Warn("Failed to update snapshot tree", "from", parentRoot, "to", root, "err", err) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index cb85a05b57..bb5db4ced1 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -65,6 +65,8 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return // Ugh, something went horribly wrong, bail out } } + // All transactions processed, finalize the block to force loading written-only trie paths + statedb.Finalise(true) // TODO(karalabe): should we run this on interrupt too? } // precacheTransaction attempts to apply a transaction to the given state database