Skip to content

Commit

Permalink
state: replace fastcache with gc-friendly structure (go-ethereum #26092)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoomee1313 committed Aug 22, 2024
1 parent 1a5fd17 commit 5686535
Show file tree
Hide file tree
Showing 11 changed files with 851 additions and 18 deletions.
8 changes: 5 additions & 3 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,10 @@ type BlockChain struct {
currentBlock atomic.Value // Current head of the block chain
currentFastBlock atomic.Value // Current head of the fast-sync chain (may be above the block chain!)

stateCache state.Database // State database to reuse between imports (contains state cache)
futureBlocks *lru.Cache // future blocks are blocks added for later processing
stateCache state.Database // State database to reuse between imports (contains state cache)

// future blocks are blocks added for later processing
futureBlocks *lru.Cache

quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
Expand Down Expand Up @@ -1083,7 +1085,7 @@ func (bc *BlockChain) Stop() {
logger.Error("Failed to journal state snapshot", "err", err)
}
}

bc.snaps.Release()
triedb := bc.stateCache.TrieDB()
if !bc.isArchiveMode() {
number := bc.CurrentBlock().NumberU64()
Expand Down
19 changes: 10 additions & 9 deletions blockchain/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import (
"errors"
"fmt"

"github.com/VictoriaMetrics/fastcache"
"github.com/kaiachain/kaia/common"
"github.com/kaiachain/kaia/common/lru"
"github.com/kaiachain/kaia/storage/database"
"github.com/kaiachain/kaia/storage/statedb"
)
Expand Down Expand Up @@ -144,7 +144,7 @@ func NewDatabaseWithNewCache(db database.DBManager, cacheConfig *statedb.TrieNod
return &cachingDB{
db: statedb.NewDatabaseWithNewCache(db, cacheConfig),
codeSizeCache: getCodeSizeCache(),
codeCache: fastcache.New(codeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
}
}

Expand All @@ -155,14 +155,14 @@ func NewDatabaseWithExistingCache(db database.DBManager, cache statedb.TrieNodeC
return &cachingDB{
db: statedb.NewDatabaseWithExistingCache(db, cache),
codeSizeCache: getCodeSizeCache(),
codeCache: fastcache.New(codeCacheSize),
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
}
}

type cachingDB struct {
db *statedb.Database
codeSizeCache common.Cache
codeCache *fastcache.Cache
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
}

// OpenTrie opens the main account trie at a specific root hash.
Expand All @@ -187,12 +187,12 @@ func (db *cachingDB) CopyTrie(t Trie) Trie {

// ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(codeHash common.Hash) ([]byte, error) {
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
if code, _ := db.codeCache.Get(codeHash); len(code) > 0 {
return code, nil
}
code := db.db.DiskDB().ReadCode(codeHash)
if len(code) > 0 {
db.codeCache.Set(codeHash.Bytes(), code)
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
Expand All @@ -201,20 +201,21 @@ func (db *cachingDB) ContractCode(codeHash common.Hash) ([]byte, error) {

// DeleteCode deletes a particular contract's code.
func (db *cachingDB) DeleteCode(codeHash common.Hash) {
db.codeCache.Del(codeHash.Bytes())
// there's no need to delete the item from the cache.
// first, it's only used in testcode, and second, it will be evicted automatically if not used
db.db.DiskDB().DeleteCode(codeHash)
}

// ContractCodeWithPrefix retrieves a particular contract's code. If the
// code can't be found in the cache, then check the existence with **new**
// db scheme.
func (db *cachingDB) ContractCodeWithPrefix(codeHash common.Hash) ([]byte, error) {
if code := db.codeCache.Get(nil, codeHash.Bytes()); len(code) > 0 {
if code, _ := db.codeCache.Get(codeHash); len(code) > 0 {
return code, nil
}
code := db.db.DiskDB().ReadCodeWithPrefix(codeHash)
if len(code) > 0 {
db.codeCache.Set(codeHash.Bytes(), code)
db.codeCache.Add(codeHash, code)
db.codeSizeCache.Add(codeHash, len(code))
return code, nil
}
Expand Down
223 changes: 223 additions & 0 deletions common/lru/basiclru.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2022 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

// Package lru implements generically-typed LRU caches.
package lru

// BasicLRU is a simple LRU cache.
//
// This type is not safe for concurrent use.
// The zero value is not valid, instances must be created using NewCache.
type BasicLRU[K comparable, V any] struct {
list *list[K]
items map[K]cacheItem[K, V]
cap int
}

type cacheItem[K any, V any] struct {
elem *listElem[K]
value V
}

// NewBasicLRU creates a new LRU cache.
func NewBasicLRU[K comparable, V any](capacity int) BasicLRU[K, V] {
if capacity <= 0 {
capacity = 1
}
c := BasicLRU[K, V]{
items: make(map[K]cacheItem[K, V]),
list: newList[K](),
cap: capacity,
}
return c
}

// Add adds a value to the cache. Returns true if an item was evicted to store the new item.
func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) {
item, ok := c.items[key]
if ok {
// Already exists in cache.
item.value = value
c.items[key] = item
c.list.moveToFront(item.elem)
return false
}

var elem *listElem[K]
if c.Len() >= c.cap {
elem = c.list.removeLast()
delete(c.items, elem.v)
evicted = true
} else {
elem = new(listElem[K])
}

// Store the new item.
// Note that, if another item was evicted, we re-use its list element here.
elem.v = key
c.items[key] = cacheItem[K, V]{elem, value}
c.list.pushElem(elem)
return evicted
}

// Contains reports whether the given key exists in the cache.
func (c *BasicLRU[K, V]) Contains(key K) bool {
_, ok := c.items[key]
return ok
}

// Get retrieves a value from the cache. This marks the key as recently used.
func (c *BasicLRU[K, V]) Get(key K) (value V, ok bool) {
item, ok := c.items[key]
if !ok {
return value, false
}
c.list.moveToFront(item.elem)
return item.value, true
}

// GetOldest retrieves the least-recently-used item.
// Note that this does not update the item's recency.
func (c *BasicLRU[K, V]) GetOldest() (key K, value V, ok bool) {
lastElem := c.list.last()
if lastElem == nil {
return key, value, false
}
key = lastElem.v
item := c.items[key]
return key, item.value, true
}

// Len returns the current number of items in the cache.
func (c *BasicLRU[K, V]) Len() int {
return len(c.items)
}

// Peek retrieves a value from the cache, but does not mark the key as recently used.
func (c *BasicLRU[K, V]) Peek(key K) (value V, ok bool) {
item, ok := c.items[key]
return item.value, ok
}

// Purge empties the cache.
func (c *BasicLRU[K, V]) Purge() {
c.list.init()
for k := range c.items {
delete(c.items, k)
}
}

// Remove drops an item from the cache. Returns true if the key was present in cache.
func (c *BasicLRU[K, V]) Remove(key K) bool {
item, ok := c.items[key]
if ok {
delete(c.items, key)
c.list.remove(item.elem)
}
return ok
}

// RemoveOldest drops the least recently used item.
func (c *BasicLRU[K, V]) RemoveOldest() (key K, value V, ok bool) {
lastElem := c.list.last()
if lastElem == nil {
return key, value, false
}

key = lastElem.v
item := c.items[key]
delete(c.items, key)
c.list.remove(lastElem)
return key, item.value, true
}

// Keys returns all keys in the cache.
func (c *BasicLRU[K, V]) Keys() []K {
keys := make([]K, 0, len(c.items))
return c.list.appendTo(keys)
}

// list is a doubly-linked list holding items of type he.
// The zero value is not valid, use newList to create lists.
type list[T any] struct {
root listElem[T]
}

type listElem[T any] struct {
next *listElem[T]
prev *listElem[T]
v T
}

func newList[T any]() *list[T] {
l := new(list[T])
l.init()
return l
}

// init reinitializes the list, making it empty.
func (l *list[T]) init() {
l.root.next = &l.root
l.root.prev = &l.root
}

// pushElem adds an element to the front of the list.
func (l *list[T]) pushElem(e *listElem[T]) {
e.prev = &l.root
e.next = l.root.next
l.root.next = e
e.next.prev = e
}

// moveToFront makes 'node' the head of the list.
func (l *list[T]) moveToFront(e *listElem[T]) {
e.prev.next = e.next
e.next.prev = e.prev
l.pushElem(e)
}

// remove removes an element from the list.
func (l *list[T]) remove(e *listElem[T]) {
e.prev.next = e.next
e.next.prev = e.prev
e.next, e.prev = nil, nil
}

// removeLast removes the last element of the list.
func (l *list[T]) removeLast() *listElem[T] {
last := l.last()
if last != nil {
l.remove(last)
}
return last
}

// last returns the last element of the list, or nil if the list is empty.
func (l *list[T]) last() *listElem[T] {
e := l.root.prev
if e == &l.root {
return nil
}
return e
}

// appendTo appends all list elements to a slice.
func (l *list[T]) appendTo(slice []T) []T {
for e := l.root.prev; e != &l.root; e = e.prev {
slice = append(slice, e.v)
}
return slice
}
Loading

0 comments on commit 5686535

Please sign in to comment.