mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
core/state: add cache statistics of contract code reader (#33532)
This commit is contained in:
parent
01b39c96bf
commit
9623dcbca2
2 changed files with 77 additions and 36 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue