Skip to content

Commit 4ff9697

Browse files
flywukongunclezoro
authored andcommitted
[R4R]add sharedStorage for prefetching to L1 (ethereum#792)
* add sharedStorage for prefetching to L1 * remote originStorage in stateObjects * fix core * fix bug of sync map * remove read lock when get & set keys * statedb copy use CopyWithSharedStorage * reduce lock access * fix comment * avoid sharedPool effects on other modules * remove tryPreload * fix comment * fix var name * fix lint * fix L1 miss data && data condition * fix comment
1 parent 5f1aabe commit 4ff9697

File tree

6 files changed

+108
-89
lines changed

6 files changed

+108
-89
lines changed

core/blockchain.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2101,7 +2101,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er
21012101
if parent == nil {
21022102
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
21032103
}
2104-
statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps)
2104+
statedb, err := state.NewWithSharedPool(parent.Root, bc.stateCache, bc.snaps)
21052105
if err != nil {
21062106
return it.index, err
21072107
}

core/state/shared_pool.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package state
2+
3+
import (
4+
"sync"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
)
8+
9+
// sharedPool is used to store maps of originStorage of stateObjects
10+
type StoragePool struct {
11+
sync.RWMutex
12+
sharedMap map[common.Address]*sync.Map
13+
}
14+
15+
func NewStoragePool() *StoragePool {
16+
sharedMap := make(map[common.Address]*sync.Map)
17+
return &StoragePool{
18+
sync.RWMutex{},
19+
sharedMap,
20+
}
21+
}
22+
23+
// getStorage Check whether the storage exist in pool,
24+
// new one if not exist, the content of storage will be fetched in stateObjects.GetCommittedState()
25+
func (s *StoragePool) getStorage(address common.Address) *sync.Map {
26+
s.RLock()
27+
storageMap, ok := s.sharedMap[address]
28+
s.RUnlock()
29+
if !ok {
30+
s.Lock()
31+
defer s.Unlock()
32+
if storageMap, ok = s.sharedMap[address]; !ok {
33+
m := new(sync.Map)
34+
s.sharedMap[address] = m
35+
return m
36+
}
37+
}
38+
return storageMap
39+
}

core/state/state_object.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"io"
2323
"math/big"
24+
"sync"
2425
"time"
2526

2627
"github.com/ethereum/go-ethereum/common"
@@ -79,7 +80,9 @@ type StateObject struct {
7980
trie Trie // storage trie, which becomes non-nil on first access
8081
code Code // contract bytecode, which gets set when code is loaded
8182

82-
originStorage Storage // Storage cache of original entries to dedup rewrites, reset for every transaction
83+
sharedOriginStorage *sync.Map // Storage cache of original entries to dedup rewrites, reset for every transaction
84+
originStorage Storage
85+
8386
pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block
8487
dirtyStorage Storage // Storage entries that have been modified in the current transaction execution
8588
fakeStorage Storage // Fake storage which constructed by caller for debugging purpose.
@@ -120,14 +123,21 @@ func newObject(db *StateDB, address common.Address, data Account) *StateObject {
120123
if data.Root == (common.Hash{}) {
121124
data.Root = emptyRoot
122125
}
126+
var storageMap *sync.Map
127+
// Check whether the storage exist in pool, new originStorage if not exist
128+
if db != nil && db.storagePool != nil {
129+
storageMap = db.GetStorage(address)
130+
}
131+
123132
return &StateObject{
124-
db: db,
125-
address: address,
126-
addrHash: crypto.Keccak256Hash(address[:]),
127-
data: data,
128-
originStorage: make(Storage),
129-
pendingStorage: make(Storage),
130-
dirtyStorage: make(Storage),
133+
db: db,
134+
address: address,
135+
addrHash: crypto.Keccak256Hash(address[:]),
136+
data: data,
137+
sharedOriginStorage: storageMap,
138+
originStorage: make(Storage),
139+
pendingStorage: make(Storage),
140+
dirtyStorage: make(Storage),
131141
}
132142
}
133143

@@ -194,6 +204,29 @@ func (s *StateObject) GetState(db Database, key common.Hash) common.Hash {
194204
return s.GetCommittedState(db, key)
195205
}
196206

207+
func (s *StateObject) getOriginStorage(key common.Hash) (common.Hash, bool) {
208+
if value, cached := s.originStorage[key]; cached {
209+
return value, true
210+
}
211+
// if L1 cache miss, try to get it from shared pool
212+
if s.sharedOriginStorage != nil {
213+
val, ok := s.sharedOriginStorage.Load(key)
214+
if !ok {
215+
return common.Hash{}, false
216+
}
217+
s.originStorage[key] = val.(common.Hash)
218+
return val.(common.Hash), true
219+
}
220+
return common.Hash{}, false
221+
}
222+
223+
func (s *StateObject) setOriginStorage(key common.Hash, value common.Hash) {
224+
if s.db.writeOnSharedStorage && s.sharedOriginStorage != nil {
225+
s.sharedOriginStorage.Store(key, value)
226+
}
227+
s.originStorage[key] = value
228+
}
229+
197230
// GetCommittedState retrieves a value from the committed account storage trie.
198231
func (s *StateObject) GetCommittedState(db Database, key common.Hash) common.Hash {
199232
// If the fake storage is set, only lookup the state here(in the debugging mode)
@@ -204,7 +237,8 @@ func (s *StateObject) GetCommittedState(db Database, key common.Hash) common.Has
204237
if value, pending := s.pendingStorage[key]; pending {
205238
return value
206239
}
207-
if value, cached := s.originStorage[key]; cached {
240+
241+
if value, cached := s.getOriginStorage(key); cached {
208242
return value
209243
}
210244
// If no live objects are available, attempt to use snapshots
@@ -263,7 +297,7 @@ func (s *StateObject) GetCommittedState(db Database, key common.Hash) common.Has
263297
}
264298
value.SetBytes(content)
265299
}
266-
s.originStorage[key] = value
300+
s.setOriginStorage(key, value)
267301
return value
268302
}
269303

@@ -320,6 +354,7 @@ func (s *StateObject) finalise(prefetch bool) {
320354
slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure
321355
}
322356
}
357+
323358
if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != emptyRoot {
324359
s.db.prefetcher.prefetch(s.data.Root, slotsToPrefetch, s.addrHash)
325360
}
@@ -356,7 +391,6 @@ func (s *StateObject) updateTrie(db Database) Trie {
356391
continue
357392
}
358393
s.originStorage[key] = value
359-
360394
var v []byte
361395
if (value == common.Hash{}) {
362396
s.setError(tr.TryDelete(key[:]))

core/state/statedb.go

Lines changed: 22 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ import (
3939
"github.com/ethereum/go-ethereum/trie"
4040
)
4141

42-
const (
43-
preLoadLimit = 128
44-
defaultNumOfSlots = 100
45-
)
42+
const defaultNumOfSlots = 100
4643

4744
type revision struct {
4845
id int
@@ -101,6 +98,8 @@ type StateDB struct {
10198
stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
10299
stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
103100

101+
storagePool *StoragePool // sharedPool to store L1 originStorage of stateObjects
102+
writeOnSharedStorage bool // Write to the shared origin storage of a stateObject while reading from the underlying storage layer.
104103
// DB error.
105104
// State objects are used by the consensus core and VM which are
106105
// unable to deal with database-level errors. Any error that occurs
@@ -147,6 +146,16 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
147146
return newStateDB(root, db, snaps)
148147
}
149148

149+
// NewWithSharedPool creates a new state with sharedStorge on layer 1.5
150+
func NewWithSharedPool(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
151+
statedb, err := newStateDB(root, db, snaps)
152+
if err != nil {
153+
return nil, err
154+
}
155+
statedb.storagePool = NewStoragePool()
156+
return statedb, nil
157+
}
158+
150159
func newStateDB(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
151160
sdb := &StateDB{
152161
db: db,
@@ -178,6 +187,10 @@ func newStateDB(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB,
178187
return sdb, nil
179188
}
180189

190+
func (s *StateDB) EnableWriteOnSharedStorage() {
191+
s.writeOnSharedStorage = true
192+
}
193+
181194
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
182195
// state trie concurrently while the state is mutated so that when we reach the
183196
// commit phase, most of the needed data is already hot.
@@ -591,78 +604,6 @@ func (s *StateDB) getStateObject(addr common.Address) *StateObject {
591604
return nil
592605
}
593606

594-
func (s *StateDB) TryPreload(block *types.Block, signer types.Signer) {
595-
accounts := make(map[common.Address]bool, block.Transactions().Len())
596-
accountsSlice := make([]common.Address, 0, block.Transactions().Len())
597-
for _, tx := range block.Transactions() {
598-
from, err := types.Sender(signer, tx)
599-
if err != nil {
600-
break
601-
}
602-
accounts[from] = true
603-
if tx.To() != nil {
604-
accounts[*tx.To()] = true
605-
}
606-
}
607-
for account := range accounts {
608-
accountsSlice = append(accountsSlice, account)
609-
}
610-
if len(accountsSlice) >= preLoadLimit && len(accountsSlice) > runtime.NumCPU() {
611-
objsChan := make(chan []*StateObject, runtime.NumCPU())
612-
for i := 0; i < runtime.NumCPU(); i++ {
613-
start := i * len(accountsSlice) / runtime.NumCPU()
614-
end := (i + 1) * len(accountsSlice) / runtime.NumCPU()
615-
if i+1 == runtime.NumCPU() {
616-
end = len(accountsSlice)
617-
}
618-
go func(start, end int) {
619-
objs := s.preloadStateObject(accountsSlice[start:end])
620-
objsChan <- objs
621-
}(start, end)
622-
}
623-
for i := 0; i < runtime.NumCPU(); i++ {
624-
objs := <-objsChan
625-
for _, obj := range objs {
626-
s.SetStateObject(obj)
627-
}
628-
}
629-
}
630-
}
631-
632-
func (s *StateDB) preloadStateObject(address []common.Address) []*StateObject {
633-
// Prefer live objects if any is available
634-
if s.snap == nil {
635-
return nil
636-
}
637-
hasher := crypto.NewKeccakState()
638-
objs := make([]*StateObject, 0, len(address))
639-
for _, addr := range address {
640-
// If no live objects are available, attempt to use snapshots
641-
if acc, err := s.snap.Account(crypto.HashData(hasher, addr.Bytes())); err == nil {
642-
if acc == nil {
643-
continue
644-
}
645-
data := &Account{
646-
Nonce: acc.Nonce,
647-
Balance: acc.Balance,
648-
CodeHash: acc.CodeHash,
649-
Root: common.BytesToHash(acc.Root),
650-
}
651-
if len(data.CodeHash) == 0 {
652-
data.CodeHash = emptyCodeHash
653-
}
654-
if data.Root == (common.Hash{}) {
655-
data.Root = emptyRoot
656-
}
657-
// Insert into the live set
658-
obj := newObject(s, addr, *data)
659-
objs = append(objs, obj)
660-
}
661-
// Do not enable this feature when snapshot is not enabled.
662-
}
663-
return objs
664-
}
665-
666607
// getDeletedStateObject is similar to getStateObject, but instead of returning
667608
// nil for a deleted state object, it returns the actual object with the deleted
668609
// flag set. This is needed by the state journal to revert to the correct s-
@@ -828,6 +769,7 @@ func (s *StateDB) Copy() *StateDB {
828769
stateObjects: make(map[common.Address]*StateObject, len(s.journal.dirties)),
829770
stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
830771
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
772+
storagePool: s.storagePool,
831773
refund: s.refund,
832774
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
833775
logSize: s.logSize,
@@ -1626,3 +1568,7 @@ func (s *StateDB) GetDirtyAccounts() []common.Address {
16261568
}
16271569
return accounts
16281570
}
1571+
1572+
func (s *StateDB) GetStorage(address common.Address) *sync.Map {
1573+
return s.storagePool.getStorage(address)
1574+
}

core/state_prefetcher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
6868
for i := 0; i < prefetchThread; i++ {
6969
go func(idx int) {
7070
newStatedb := statedb.Copy()
71+
newStatedb.EnableWriteOnSharedStorage()
7172
gaspool := new(GasPool).AddGas(block.GasLimit())
7273
blockContext := NewEVMBlockContext(header, p.bc, nil)
7374
evm := vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)

core/state_processor.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
385385
gp = new(GasPool).AddGas(block.GasLimit())
386386
)
387387
signer := types.MakeSigner(p.bc.chainConfig, block.Number())
388-
statedb.TryPreload(block, signer)
389388
var receipts = make([]*types.Receipt, 0)
390389
// Mutate the block and state according to any hard-fork specs
391390
if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 {

0 commit comments

Comments
 (0)