From 9623dcbca2ddca88253eef5695914a61bbaa2e07 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Thu, 8 Jan 2026 11:48:45 +0800 Subject: [PATCH] core/state: add cache statistics of contract code reader (#33532) --- core/state/database.go | 29 ++++++++++----- core/state/reader.go | 84 +++++++++++++++++++++++++++++------------- 2 files changed, 77 insertions(+), 36 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 1e8fc9d5c9..4a5547d075 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -177,8 +177,8 @@ func NewDatabaseForTesting() *CachingDB { return NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil) } -// Reader returns a state reader associated with the specified state root. -func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { +// StateReader returns a state reader associated with the specified state root. +func (db *CachingDB) StateReader(stateRoot common.Hash) (StateReader, error) { var readers []StateReader // Configure the state reader using the standalone snapshot in hash mode. @@ -208,23 +208,32 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { } readers = append(readers, tr) - combined, err := newMultiStateReader(readers...) + return newMultiStateReader(readers...) +} + +// Reader implements Database, returning a reader associated with the specified +// state root. +func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { + sr, err := db.StateReader(stateRoot) if err != nil { return nil, err } - return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), combined), nil + return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), sr), nil } -// ReadersWithCacheStats creates a pair of state readers sharing the same internal cache and -// same backing Reader, but exposing separate statistics. -// and statistics. +// ReadersWithCacheStats creates a pair of state readers that share the same +// underlying state reader and internal state cache, while maintaining separate +// statistics respectively. func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { - reader, err := db.Reader(stateRoot) + r, err := db.StateReader(stateRoot) if err != nil { return nil, nil, err } - shared := newReaderWithCache(reader) - return newReaderWithCacheStats(shared), newReaderWithCacheStats(shared), nil + sr := newStateReaderWithCache(r) + + ra := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)) + rb := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)) + return ra, rb, nil } // OpenTrie opens the main account trie at a specific root hash. diff --git a/core/state/reader.go b/core/state/reader.go index 38228f8453..2db9d1f9b4 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -58,6 +58,13 @@ type ContractCodeReader interface { CodeSize(addr common.Address, codeHash common.Hash) (int, error) } +// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to +// expose statistics of code reader. +type ContractCodeReaderWithStats interface { + ContractCodeReader + GetStats() (int64, int64) +} + // StateReader defines the interface for accessing accounts and storage slots // associated with a specific state. // @@ -97,6 +104,8 @@ type ReaderStats struct { AccountCacheMiss int64 StorageCacheHit int64 StorageCacheMiss int64 + ContractCodeHit int64 + ContractCodeMiss int64 } // String implements fmt.Stringer, returning string format statistics. @@ -104,6 +113,7 @@ func (s ReaderStats) String() string { var ( accountCacheHitRate float64 storageCacheHitRate float64 + contractCodeHitRate float64 ) if s.AccountCacheHit > 0 { accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100 @@ -111,9 +121,13 @@ func (s ReaderStats) String() string { if s.StorageCacheHit > 0 { storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100 } + if s.ContractCodeHit > 0 { + contractCodeHitRate = float64(s.ContractCodeHit) / float64(s.ContractCodeHit+s.ContractCodeMiss) * 100 + } msg := fmt.Sprintf("Reader statistics\n") msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate) msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate) + msg += fmt.Sprintf("code: hit: %d, miss: %d, rate: %.2f\n", s.ContractCodeHit, s.ContractCodeMiss, contractCodeHitRate) return msg } @@ -134,6 +148,10 @@ type cachingCodeReader struct { // they are natively thread-safe. codeCache *lru.SizeConstrainedCache[common.Hash, []byte] codeSizeCache *lru.Cache[common.Hash, int] + + // Cache statistics + hit atomic.Int64 // Number of code lookups found in the cache. + miss atomic.Int64 // Number of code lookups not found in the cache. } // newCachingCodeReader constructs the code reader. @@ -150,8 +168,11 @@ func newCachingCodeReader(db ethdb.KeyValueReader, codeCache *lru.SizeConstraine func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) { code, _ := r.codeCache.Get(codeHash) if len(code) > 0 { + r.hit.Add(1) return code, nil } + r.miss.Add(1) + code = rawdb.ReadCode(r.db, codeHash) if len(code) > 0 { r.codeCache.Add(codeHash, code) @@ -164,6 +185,7 @@ func (r *cachingCodeReader) Code(addr common.Address, codeHash common.Hash) ([]b // If the contract code doesn't exist, no error will be returned. func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) { if cached, ok := r.codeSizeCache.Get(codeHash); ok { + r.hit.Add(1) return cached, nil } code, err := r.Code(addr, codeHash) @@ -180,6 +202,11 @@ func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool return len(code) > 0 } +// GetStats returns the cache statistics fo the code reader. +func (r *cachingCodeReader) GetStats() (int64, int64) { + return r.hit.Load(), r.miss.Load() +} + // flatReader wraps a database state reader and is safe for concurrent access. type flatReader struct { reader database.StateReader @@ -462,10 +489,10 @@ func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader { } } -// readerWithCache is a wrapper around Reader that maintains additional state caches -// to support concurrent state access. -type readerWithCache struct { - Reader // safe for concurrent read +// stateReaderWithCache is a wrapper around StateReader that maintains additional +// state caches to support concurrent state access. +type stateReaderWithCache struct { + StateReader // Previously resolved state entries. accounts map[common.Address]*types.StateAccount @@ -481,11 +508,11 @@ type readerWithCache struct { } } -// newReaderWithCache constructs the reader with local cache. -func newReaderWithCache(reader Reader) *readerWithCache { - r := &readerWithCache{ - Reader: reader, - accounts: make(map[common.Address]*types.StateAccount), +// newStateReaderWithCache constructs the state reader with local cache. +func newStateReaderWithCache(sr StateReader) *stateReaderWithCache { + r := &stateReaderWithCache{ + StateReader: sr, + accounts: make(map[common.Address]*types.StateAccount), } for i := range r.storageBuckets { r.storageBuckets[i].storages = make(map[common.Address]map[common.Hash]common.Hash) @@ -498,7 +525,7 @@ func newReaderWithCache(reader Reader) *readerWithCache { // 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, bool, error) { +func (r *stateReaderWithCache) 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] @@ -507,7 +534,7 @@ func (r *readerWithCache) account(addr common.Address) (*types.StateAccount, boo return acct, true, nil } // Try to resolve the requested account from the underlying reader - acct, err := r.Reader.Account(addr) + acct, err := r.StateReader.Account(addr) if err != nil { return nil, false, err } @@ -521,7 +548,7 @@ func (r *readerWithCache) account(addr common.Address) (*types.StateAccount, boo // 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 *stateReaderWithCache) Account(addr common.Address) (*types.StateAccount, error) { account, _, err := r.account(addr) return account, err } @@ -529,7 +556,7 @@ func (r *readerWithCache) Account(addr common.Address) (*types.StateAccount, 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) { +func (r *stateReaderWithCache) storage(addr common.Address, slot common.Hash) (common.Hash, bool, error) { var ( value common.Hash ok bool @@ -546,7 +573,7 @@ func (r *readerWithCache) storage(addr common.Address, slot common.Hash) (common return value, true, nil } // Try to resolve the requested storage slot from the underlying reader - value, err := r.Reader.Storage(addr, slot) + value, err := r.StateReader.Storage(addr, slot) if err != nil { return common.Hash{}, false, err } @@ -567,13 +594,14 @@ func (r *readerWithCache) storage(addr common.Address, slot common.Hash) (common // 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 *stateReaderWithCache) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { value, _, err := r.storage(addr, slot) return value, err } -type readerWithCacheStats struct { - *readerWithCache +type readerWithStats struct { + *stateReaderWithCache + ContractCodeReaderWithStats accountCacheHit atomic.Int64 accountCacheMiss atomic.Int64 @@ -581,10 +609,11 @@ type readerWithCacheStats struct { storageCacheMiss atomic.Int64 } -// newReaderWithCacheStats constructs the reader with additional statistics tracked. -func newReaderWithCacheStats(reader *readerWithCache) *readerWithCacheStats { - return &readerWithCacheStats{ - readerWithCache: reader, +// newReaderWithStats constructs the reader with additional statistics tracked. +func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats) *readerWithStats { + return &readerWithStats{ + stateReaderWithCache: sr, + ContractCodeReaderWithStats: cr, } } @@ -592,8 +621,8 @@ func newReaderWithCacheStats(reader *readerWithCache) *readerWithCacheStats { // 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) +func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, error) { + account, incache, err := r.stateReaderWithCache.account(addr) if err != nil { return nil, err } @@ -610,8 +639,8 @@ func (r *readerWithCacheStats) Account(addr common.Address) (*types.StateAccount // 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) +func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { + value, incache, err := r.stateReaderWithCache.storage(addr, slot) if err != nil { return common.Hash{}, err } @@ -624,11 +653,14 @@ func (r *readerWithCacheStats) Storage(addr common.Address, slot common.Hash) (c } // GetStats implements ReaderWithStats, returning the statistics of state reader. -func (r *readerWithCacheStats) GetStats() ReaderStats { +func (r *readerWithStats) GetStats() ReaderStats { + codeHit, codeMiss := r.ContractCodeReaderWithStats.GetStats() return ReaderStats{ AccountCacheHit: r.accountCacheHit.Load(), AccountCacheMiss: r.accountCacheMiss.Load(), StorageCacheHit: r.storageCacheHit.Load(), StorageCacheMiss: r.storageCacheMiss.Load(), + ContractCodeHit: codeHit, + ContractCodeMiss: codeMiss, } }