This commit is contained in:
Jürgen Gamenik 2025-06-21 18:07:33 +02:00
commit f2b47391de
4 changed files with 140 additions and 20 deletions

View file

@ -77,6 +77,16 @@ var (
storageUpdateTimer = metrics.NewRegisteredResettingTimer("chain/storage/updates", nil)
storageCommitTimer = metrics.NewRegisteredResettingTimer("chain/storage/commits", nil)
accountCacheHitMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/hit", nil)
accountCacheMissMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/process/miss", nil)
storageCacheHitMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/process/hit", nil)
storageCacheMissMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/process/miss", nil)
accountCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/prefetch/hit", nil)
accountCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/account/reads/cache/prefetch/miss", nil)
storageCacheHitPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/hit", nil)
storageCacheMissPrefetchMeter = metrics.NewRegisteredMeter("chain/storage/reads/cache/prefetch/miss", nil)
accountReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/account/single/reads", nil)
storageReadSingleTimer = metrics.NewRegisteredResettingTimer("chain/storage/single/reads", nil)
@ -1944,18 +1954,32 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
//
// Note: the main processor and prefetcher share the same reader with a local
// cache for mitigating the overhead of state access.
reader, err := bc.statedb.ReaderWithCache(parentRoot)
prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot)
if err != nil {
return nil, err
}
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, reader)
throwaway, err := state.NewWithReader(parentRoot, bc.statedb, prefetch)
if err != nil {
return nil, err
}
statedb, err = state.NewWithReader(parentRoot, bc.statedb, reader)
statedb, err = state.NewWithReader(parentRoot, bc.statedb, process)
if err != nil {
return nil, err
}
// Upload the statistics of reader at the end
defer func() {
stats := prefetch.GetStats()
accountCacheHitPrefetchMeter.Mark(stats.AccountHit)
accountCacheMissPrefetchMeter.Mark(stats.AccountMiss)
storageCacheHitPrefetchMeter.Mark(stats.StorageHit)
storageCacheMissPrefetchMeter.Mark(stats.StorageMiss)
stats = process.GetStats()
accountCacheHitMeter.Mark(stats.AccountHit)
accountCacheMissMeter.Mark(stats.AccountMiss)
storageCacheHitMeter.Mark(stats.StorageHit)
storageCacheMissMeter.Mark(stats.StorageMiss)
}()
go func(start time.Time, throwaway *state.StateDB, block *types.Block) {
// Disable tracing for prefetcher executions.
vmCfg := bc.cfg.VmConfig

View file

@ -209,13 +209,16 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil
}
// ReaderWithCache creates a state reader with internal local cache.
func (db *CachingDB) ReaderWithCache(stateRoot common.Hash) (Reader, error) {
// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and
// same backing Reader, but exposing separate statistics.
// and statistics.
func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) {
reader, err := db.Reader(stateRoot)
if err != nil {
return nil, err
return nil, nil, err
}
return newReaderWithCache(reader), nil
shared := newReaderWithCache(reader)
return newReaderWithCacheStats(shared), newReaderWithCacheStats(shared), nil
}
// OpenTrie opens the main account trie at a specific root hash.

View file

@ -19,6 +19,7 @@ package state
import (
"errors"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
@ -82,6 +83,20 @@ type Reader interface {
StateReader
}
// ReaderStats wraps the statistics of reader.
type ReaderStats struct {
AccountHit int64
AccountMiss int64
StorageHit int64
StorageMiss int64
}
// ReaderWithStats wraps the additional method to retrieve the reader statistics from.
type ReaderWithStats interface {
Reader
GetStats() ReaderStats
}
// cachingCodeReader implements ContractCodeReader, accessing contract code either in
// local key-value store or the shared code cache.
//
@ -414,35 +429,43 @@ func newReaderWithCache(reader Reader) *readerWithCache {
return r
}
// Account implements StateReader, retrieving the account specified by the address.
// The returned account might be nil if it's not existent.
// account retrieves the account specified by the address along with a flag
// indicating whether it's found in the cache or not. The returned account
// might be nil if it's not existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, error) {
func (r *readerWithCache) account(addr common.Address) (*types.StateAccount, bool, error) {
// Try to resolve the requested account in the local cache
r.accountLock.RLock()
acct, ok := r.accounts[addr]
r.accountLock.RUnlock()
if ok {
return acct, nil
return acct, true, nil
}
// Try to resolve the requested account from the underlying reader
acct, err := r.Reader.Account(addr)
if err != nil {
return nil, err
return nil, false, err
}
r.accountLock.Lock()
r.accounts[addr] = acct
r.accountLock.Unlock()
return acct, nil
return acct, false, nil
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. The returned storage slot might be empty if it's not
// existent.
// Account implements StateReader, retrieving the account specified by the address.
// The returned account might be nil if it's not existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, error) {
account, _, err := r.account(addr)
return account, err
}
// storage retrieves the storage slot specified by the address and slot key, along
// with a flag indicating whether it's found in the cache or not. The returned
// storage slot might be empty if it's not existent.
func (r *readerWithCache) storage(addr common.Address, slot common.Hash) (common.Hash, bool, error) {
var (
value common.Hash
ok bool
@ -456,12 +479,12 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
}
bucket.lock.RUnlock()
if ok {
return value, nil
return value, true, nil
}
// Try to resolve the requested storage slot from the underlying reader
value, err := r.Reader.Storage(addr, slot)
if err != nil {
return common.Hash{}, err
return common.Hash{}, false, err
}
bucket.lock.Lock()
slots, ok = bucket.storages[addr]
@ -472,5 +495,75 @@ func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common
slots[slot] = value
bucket.lock.Unlock()
return value, false, nil
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. The returned storage slot might be empty if it's not
// existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
value, _, err := r.storage(addr, slot)
return value, err
}
type readerWithCacheStats struct {
*readerWithCache
accountHit atomic.Int64
accountMiss atomic.Int64
storageHit atomic.Int64
storageMiss atomic.Int64
}
// newReaderWithCacheStats constructs the reader with additional statistics tracked.
func newReaderWithCacheStats(reader *readerWithCache) *readerWithCacheStats {
return &readerWithCacheStats{
readerWithCache: reader,
}
}
// Account implements StateReader, retrieving the account specified by the address.
// The returned account might be nil if it's not existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCacheStats) Account(addr common.Address) (*types.StateAccount, error) {
account, incache, err := r.readerWithCache.account(addr)
if err != nil {
return nil, err
}
if incache {
r.accountHit.Add(1)
} else {
r.accountMiss.Add(1)
}
return account, nil
}
// Storage implements StateReader, retrieving the storage slot specified by the
// address and slot key. The returned storage slot might be empty if it's not
// existent.
//
// An error will be returned if the state is corrupted in the underlying reader.
func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) {
value, incache, err := r.readerWithCache.storage(addr, slot)
if err != nil {
return common.Hash{}, err
}
if incache {
r.storageHit.Add(1)
} else {
r.storageMiss.Add(1)
}
return value, nil
}
// GetStats implements ReaderWithStats, returning the statistics of state reader.
func (r *readerWithCacheStats) GetStats() ReaderStats {
return ReaderStats{
AccountHit: r.accountHit.Load(),
AccountMiss: r.accountMiss.Load(),
StorageHit: r.storageHit.Load(),
StorageMiss: r.storageMiss.Load(),
}
}

View file

@ -57,7 +57,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
workers errgroup.Group
reader = statedb.Reader()
)
workers.SetLimit(runtime.NumCPU() / 2)
workers.SetLimit(4 * runtime.NumCPU() / 5) // Aggressively run the prefetching
// Iterate over and process the individual transactions
for i, tx := range block.Transactions() {