From f8be5d9367e1ff26f05d0a9e63cae493caf092f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 2 Nov 2016 13:36:13 +0200 Subject: [PATCH] cmd, core/state, eth, trie: implement memcache for the direct cache --- cmd/geth/chaincmd.go | 12 +++-- core/state/statedb.go | 21 ++++++--- eth/db_upgrade.go | 2 +- trie/directcache.go | 107 ++++++++++++++++++++++++++++-------------- 4 files changed, 95 insertions(+), 47 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 51effb5b05..2ced06f8c8 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -134,11 +134,13 @@ func importChain(ctx *cli.Context) error { utils.Fatalf("Failed to read database stats: %v", err) } fmt.Println(stats) - fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses()) - fmt.Printf("Trie cache unloads: %d\n", trie.CacheUnloads()) - fmt.Printf("Direct cache reads: %d\n", trie.DirectCacheReads()) - fmt.Printf("Direct cache writes: %d\n", trie.DirectCacheWrites()) - fmt.Printf("Direct cache misses: %d\n\n", trie.DirectCacheMisses()) + fmt.Printf("Trie cache misses: %d\n", trie.CacheMisses()) + fmt.Printf("Trie cache unloads: %d\n", trie.CacheUnloads()) + fmt.Printf("Direct memory cache hits: %d\n", trie.DirectCacheMemcacheHits()) + fmt.Printf("Direct memory cache misses: %d\n", trie.DirectCacheMemcacheMisses()) + fmt.Printf("Direct database cache reads: %d\n", trie.DirectCacheReads()) + fmt.Printf("Direct database cache writes: %d\n", trie.DirectCacheWrites()) + fmt.Printf("Direct database cache misses: %d\n\n", trie.DirectCacheMisses()) // Print the memory statistics used by the importing mem := new(runtime.MemStats) diff --git a/core/state/statedb.go b/core/state/statedb.go index 94e2bc2af2..b16decb17e 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -46,6 +46,9 @@ const ( // Number of codehash->size associations to keep. codeSizeCacheSize = 100000 + + // Number of recent accessed accounts to keep in memory + accountsMemoryCacheSize = 4096 ) type revision struct { @@ -59,12 +62,13 @@ type revision struct { // * Contracts // * Accounts type StateDB struct { - db ethdb.Database - trie *trie.Trie - storage *trie.SecureTrie - cacheComplete bool // True if we know that the directcache is complete - pastTries []*trie.Trie - codeSizeCache *lru.Cache + db ethdb.Database + trie *trie.Trie + storage *trie.SecureTrie + cacheComplete bool // True if we know that the directcache is complete + pastTries []*trie.Trie + codeSizeCache *lru.Cache + accountsMemCache *lru.ARCCache // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*StateObject @@ -95,10 +99,12 @@ func New(root common.Hash, db ethdb.Database) (*StateDB, error) { } csc, _ := lru.New(codeSizeCacheSize) + amc, _ := lru.NewARC(accountsMemoryCacheSize) ret := &StateDB{ db: db, trie: tr, codeSizeCache: csc, + accountsMemCache: amc, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), refund: new(big.Int), @@ -122,6 +128,7 @@ func (self *StateDB) New(root common.Hash) (*StateDB, error) { db: self.db, trie: tr, codeSizeCache: self.codeSizeCache, + accountsMemCache: self.accountsMemCache, stateObjects: make(map[common.Address]*StateObject), stateObjectsDirty: make(map[common.Address]struct{}), refund: new(big.Int), @@ -193,7 +200,7 @@ func (self *StateDB) SetBlockContext(blockHash common.Hash, blockNum uint64, val } } - storage := trie.NewDirectCache(self.trie, self.db, DirectCachePrefix, blockNum, blockHash, validator, self.cacheComplete) + storage := trie.NewDirectCache(self.trie, self.db, self.accountsMemCache, DirectCachePrefix, blockNum, blockHash, validator, self.cacheComplete) self.storage = trie.NewSecure(storage, self.db) } diff --git a/eth/db_upgrade.go b/eth/db_upgrade.go index ecf8c4dd95..9794675197 100644 --- a/eth/db_upgrade.go +++ b/eth/db_upgrade.go @@ -371,7 +371,7 @@ func populateDirectCache(db ethdb.Database, bc *core.BlockChain) error { if err != nil { return err } - dc := trie.NewDirectCache(tr, db, state.DirectCachePrefix, blockNum, blockHash, bc, false) + dc := trie.NewDirectCache(tr, db, nil, state.DirectCachePrefix, blockNum, blockHash, bc, false) go func() { for core.GetBlockNumber(db, core.GetHeadBlockHash(db)) < blockNum+8 { time.Sleep(10 * time.Second) diff --git a/trie/directcache.go b/trie/directcache.go index c7e114c1bc..cf1c87b942 100644 --- a/trie/directcache.go +++ b/trie/directcache.go @@ -17,27 +17,31 @@ package trie import ( - "fmt" "errors" - "time" + "fmt" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" + lru "github.com/hashicorp/golang-lru" ) var ( - directCacheLock = &sync.Mutex{} - directCacheLocked = false - directCacheWrites = metrics.NewCounter("directcache/writes") - directCacheHits = metrics.NewCounter("directcache/hits") - directCacheMisses = metrics.NewCounter("directcache/misses") - directCacheTimer = metrics.NewTimer("directcache/timer") - NotFound = errors.New("Cache entry not found") - MigrationPrefix = []byte("directstatecachemigration:") + directCacheLock = &sync.Mutex{} + directCacheLocked = false + directCacheWrites = metrics.NewCounter("directcache/writes") + directCacheHits = metrics.NewCounter("directcache/hits") + directCacheMisses = metrics.NewCounter("directcache/misses") + directCacheMemHits = metrics.NewCounter("directcache/memcache/hits") + directCacheMemMisses = metrics.NewCounter("directcache/memcache/misses") + directCacheTimer = metrics.NewTimer("directcache/timer") + + NotFound = errors.New("Cache entry not found") + MigrationPrefix = []byte("directstatecachemigration:") ) type MigrationStatus int @@ -54,8 +58,10 @@ type CacheValidator interface { } type DirectCache struct { - data PersistentMap - db Database + data PersistentMap + db Database + memcache *lru.ARCCache + keyPrefix []byte blockNum uint64 blockHash common.Hash @@ -70,10 +76,14 @@ func (cv *NullCacheValidator) IsCanonChainBlock(num uint64, hash common.Hash) bo return false } -func NewDirectCache(pm PersistentMap, db Database, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache { +func NewDirectCache(pm PersistentMap, db Database, memcache *lru.ARCCache, keyPrefix []byte, blockNum uint64, blockHash common.Hash, validator CacheValidator, complete bool) *DirectCache { + if memcache == nil { + memcache, _ = lru.NewARC(0) + } return &DirectCache{ data: pm, db: db, + memcache: memcache, keyPrefix: keyPrefix, blockNum: blockNum, blockHash: blockHash, @@ -125,17 +135,28 @@ func (dc *DirectCache) TryGet(key []byte) ([]byte, error) { } func (dc *DirectCache) getCached(key []byte) ([]byte, bool) { - data, blockNum, blockHash, err := GetDirectCache(dc.keyPrefix, key, dc.db) - if err != nil { - if err == NotFound { - return nil, dc.complete - } - glog.Errorf("Error retrieving direct cache data: %v", err) - return nil, false + // Try to retrieve the object from the memcache + var entry *cachedValue + if cached, ok := dc.memcache.Get(string(key)); ok { + directCacheMemHits.Inc(1) + entry = cached.(*cachedValue) } + // If the memcache missed, reach down to the database + var err error + if entry == nil { + directCacheMemMisses.Inc(1) - canonical := dc.blockNum > 0 && blockNum < dc.blockNum && dc.validator.IsCanonChainBlock(blockNum, blockHash) - return data, canonical + if entry, err = getDirectCache(dc.keyPrefix, key, dc.db); err != nil { + if err == NotFound { + return nil, dc.complete + } + glog.Errorf("Error retrieving direct cache data: %v", err) + return nil, false + } + dc.memcache.Add(string(key), entry) + } + canonical := dc.blockNum > 0 && entry.BlockNum < dc.blockNum && dc.validator.IsCanonChainBlock(entry.BlockNum, entry.BlockHash) + return entry.Value, canonical } func (dc *DirectCache) Update(key, value []byte) { @@ -170,6 +191,7 @@ func (dc *DirectCache) CommitTo(dbw DatabaseWriter) (root common.Hash, err error if err := WriteDirectCache(dc.keyPrefix, []byte(k), v, dc.blockNum, dc.blockHash, dbw); err != nil { return err } + dc.memcache.Add(k, &cachedValue{v, dc.blockNum, dc.blockHash}) } return nil }); err != nil { @@ -201,7 +223,7 @@ func (dc *DirectCache) Populate() (err error) { } i += 1 - if i % 10000 == 0 && glog.V(logger.Info) { + if i%10000 == 0 && glog.V(logger.Info) { glog.V(logger.Info).Infof("Constructing direct cache: processed %v entries, written %v", i, writes) } } @@ -214,7 +236,7 @@ type cachedValue struct { BlockHash common.Hash } -func DirectCacheTransaction(tx func() (error)) error { +func DirectCacheTransaction(tx func() error) error { directCacheLock.Lock() directCacheLocked = true defer func() { @@ -241,22 +263,31 @@ func WriteDirectCache(prefix, key, value []byte, number uint64, hash common.Hash return dbw.Put(append(prefix, key...), enc) } +// getDirectCache retrieves a value node directly from the database along with +// block metadata to validate its relevancy. +func getDirectCache(prefix, key []byte, db Database) (*cachedValue, error) { + defer func(start time.Time) { directCacheTimer.UpdateSince(start) }(time.Now()) + + enc, _ := db.Get(append(prefix, key...)) + if len(enc) == 0 { + return nil, NotFound + } + data := new(cachedValue) + if err := rlp.DecodeBytes(enc, data); err != nil { + return nil, fmt.Errorf("Can't decode cached object at %x: %v", key, err) + } + return data, nil +} + // GetDirectCache retrieves a value node directly from the database along with // block metadata to validate its relevancy. // // The method is meant to be used by code that circumvents the state database // and its integrated cache, namely during fast sync and database upgrades. func GetDirectCache(prefix, key []byte, db Database) ([]byte, uint64, common.Hash, error) { - defer func(start time.Time) { directCacheTimer.UpdateSince(start) }(time.Now()) - - enc, _ := db.Get(append(prefix, key...)) - if len(enc) == 0 { - return nil, 0, common.Hash{}, NotFound - } - - var data cachedValue - if err := rlp.DecodeBytes(enc, &data); err != nil { - return nil, 0, common.Hash{}, fmt.Errorf("Can't decode cached object at %x: %v", key, err) + data, err := getDirectCache(prefix, key, db) + if err != nil { + return nil, 0, common.Hash{}, err } return data.Value, data.BlockNum, data.BlockHash, nil } @@ -317,3 +348,11 @@ func DirectCacheWrites() int64 { func DirectCacheMisses() int64 { return directCacheMisses.Count() } + +func DirectCacheMemcacheHits() int64 { + return directCacheMemHits.Count() +} + +func DirectCacheMemcacheMisses() int64 { + return directCacheMemMisses.Count() +}