core/state: add cache statistics of contract code reader (#33532)

This commit is contained in:
rjl493456442 2026-01-08 11:48:45 +08:00 committed by GitHub
parent 01b39c96bf
commit 9623dcbca2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 77 additions and 36 deletions

View file

@ -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.

View file

@ -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,
}
}