From 8dbe691f6d0ac486cab32e58bd22307628e8e234 Mon Sep 17 00:00:00 2001 From: dbadoy4874 Date: Thu, 25 Aug 2022 22:01:23 +0900 Subject: [PATCH 1/5] core: use LRU cache in txNoncer --- core/tx_noncer.go | 55 ++++++++-------- core/tx_noncer_test.go | 145 +++++++++++++++++++++++++++++++++++++++++ core/tx_pool.go | 3 - 3 files changed, 173 insertions(+), 30 deletions(-) create mode 100644 core/tx_noncer_test.go diff --git a/core/tx_noncer.go b/core/tx_noncer.go index d6d220077507..575fc6942f23 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -21,22 +21,26 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" + lru "github.com/hashicorp/golang-lru" ) -// txNoncer is a tiny virtual state database to manage the executable nonces of +const nonceCacheSize = 1024 * 50 + +// txNoncer is a LRU cache to manage the executable nonces of // accounts in the pool, falling back to reading from a real state database if // an account is unknown. type txNoncer struct { fallback *state.StateDB - nonces map[common.Address]uint64 + nonces *lru.Cache lock sync.Mutex } -// newTxNoncer creates a new virtual state database to track the pool nonces. +// newTxNoncer creates a new LRU cache to track the pool nonces. func newTxNoncer(statedb *state.StateDB) *txNoncer { + cache, _ := lru.New(nonceCacheSize) return &txNoncer{ fallback: statedb.Copy(), - nonces: make(map[common.Address]uint64), + nonces: cache, } } @@ -45,43 +49,40 @@ func newTxNoncer(statedb *state.StateDB) *txNoncer { func (txn *txNoncer) get(addr common.Address) uint64 { // We use mutex for get operation is the underlying // state will mutate db even for read access. - txn.lock.Lock() - defer txn.lock.Unlock() - - if _, ok := txn.nonces[addr]; !ok { - txn.nonces[addr] = txn.fallback.GetNonce(addr) + if _, ok := txn.nonces.Get(addr); !ok { + txn.lock.Lock() + txn.nonces.Add(addr, txn.fallback.GetNonce(addr)) + txn.lock.Unlock() } - return txn.nonces[addr] + nonce, _ := txn.nonces.Get(addr) + return nonce.(uint64) } -// set inserts a new virtual nonce into the virtual state database to be returned +// set inserts a new virtual nonce into the LRU cache to be returned // whenever the pool requests it instead of reaching into the real state database. func (txn *txNoncer) set(addr common.Address, nonce uint64) { - txn.lock.Lock() - defer txn.lock.Unlock() - - txn.nonces[addr] = nonce + txn.nonces.Add(addr, nonce) } -// setIfLower updates a new virtual nonce into the virtual state database if the +// setIfLower updates a new virtual nonce into the LRU cache if the // the new one is lower. func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) { - txn.lock.Lock() - defer txn.lock.Unlock() - - if _, ok := txn.nonces[addr]; !ok { - txn.nonces[addr] = txn.fallback.GetNonce(addr) + if _, ok := txn.nonces.Get(addr); !ok { + txn.lock.Lock() + txn.nonces.Add(addr, txn.fallback.GetNonce(addr)) + txn.lock.Unlock() } - if txn.nonces[addr] <= nonce { + + cachedNonce, _ := txn.nonces.Get(addr) + if cachedNonce.(uint64) <= nonce { return } - txn.nonces[addr] = nonce + txn.nonces.Add(addr, nonce) } // setAll sets the nonces for all accounts to the given map. func (txn *txNoncer) setAll(all map[common.Address]uint64) { - txn.lock.Lock() - defer txn.lock.Unlock() - - txn.nonces = all + for addr, nonce := range all { + txn.nonces.Add(addr, nonce) + } } diff --git a/core/tx_noncer_test.go b/core/tx_noncer_test.go new file mode 100644 index 000000000000..e4a7db2cf31d --- /dev/null +++ b/core/tx_noncer_test.go @@ -0,0 +1,145 @@ +package core + +import ( + "fmt" + "math/rand" + "runtime" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/state" + lru "github.com/hashicorp/golang-lru" +) + +type txNoncerMap struct { + fallback *state.StateDB + nonces map[common.Address]uint64 + lock sync.Mutex +} + +func (txn *txNoncerMap) get(addr common.Address) uint64 { + txn.lock.Lock() + defer txn.lock.Unlock() + + if _, ok := txn.nonces[addr]; !ok { + txn.nonces[addr] = txn.fallback.GetNonce(addr) + } + return txn.nonces[addr] +} + +func (txn *txNoncerMap) set(addr common.Address, nonce uint64) { + txn.lock.Lock() + defer txn.lock.Unlock() + + txn.nonces[addr] = nonce +} + +type txNoncerLRU struct { + fallback *state.StateDB + nonces *lru.Cache +} + +func newTxNoncerMap(statedb *state.StateDB) *txNoncerMap { + return &txNoncerMap{ + fallback: statedb.Copy(), + nonces: make(map[common.Address]uint64), + } +} + +func newTxNoncerLRU(statedb *state.StateDB) *txNoncerLRU { + // lru cache size 1024 * 50 allocated 10 ~ 20 MB + cache, _ := lru.New(1024 * 50) + return &txNoncerLRU{ + fallback: statedb.Copy(), + nonces: cache, + } +} + +func TestTxNoncerMap(t *testing.T) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + txNoncer := newTxNoncerMap(statedb) + + var m runtime.MemStats + runtime.GC() + for i := 0; i < 10000000; i++ { + var b [20]byte + rand.Read(b[:]) + addr := common.Address(b) + txNoncer.set(addr, uint64(0)) + } + runtime.ReadMemStats(&m) + fmt.Printf("Object memory: %.3f MB current\n", float64(m.Alloc)/1024/1024) + fmt.Printf("System memory: %.3f MB current\n", float64(m.Sys)/1024/1024) + fmt.Printf("Allocations: %.3f million\n", float64(m.Mallocs)/1000000) +} + +func TestTxNoncerLRU(t *testing.T) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + txNoncer := newTxNoncerLRU(statedb) + + var m runtime.MemStats + runtime.GC() + for i := 0; i < 10000000; i++ { + var b [20]byte + rand.Read(b[:]) + addr := common.Address(b) + txNoncer.nonces.Add(addr, uint64(1)) + } + runtime.ReadMemStats(&m) + fmt.Printf("Object memory: %.3f MB current\n", float64(m.Alloc)/1024/1024) + fmt.Printf("System memory: %.3f MB current\n", float64(m.Sys)/1024/1024) + fmt.Printf("Allocations: %.3f million\n", float64(m.Mallocs)/1000000) +} + +func BenchmarkMapStore(b *testing.B) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + txNoncer := newTxNoncerMap(statedb) + for i := 0; i < b.N; i++ { + var byt [20]byte + rand.Read(byt[:]) + addr := common.Address(byt) + txNoncer.set(addr, uint64(1)) + } +} + +func BenchmarkLRUStore(b *testing.B) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + txNoncer := newTxNoncerLRU(statedb) + for i := 0; i < b.N; i++ { + var byt [20]byte + rand.Read(byt[:]) + addr := common.Address(byt) + txNoncer.nonces.Add(addr, uint64(1)) + } +} + +func BenchmarkMapLoad(b *testing.B) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + txNoncer := newTxNoncerMap(statedb) + var byt [20]byte + rand.Read(byt[:]) + addr := common.Address(byt) + txNoncer.set(addr, uint64(1)) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = txNoncer.get(addr) + } +} + +func BenchmarkLRULoad(b *testing.B) { + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) + txNoncer := newTxNoncerLRU(statedb) + var byt [20]byte + rand.Read(byt[:]) + addr := common.Address(byt) + txNoncer.nonces.Add(addr, uint64(1)) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + v, _ := txNoncer.nonces.Get(addr) + _ = v.(uint64) + } +} diff --git a/core/tx_pool.go b/core/tx_pool.go index 1c25442dd9c5..8ef0f5fff673 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -463,9 +463,6 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { // Nonce returns the next nonce of an account, with all transactions executable // by the pool already applied on top. func (pool *TxPool) Nonce(addr common.Address) uint64 { - pool.mu.RLock() - defer pool.mu.RUnlock() - return pool.pendingNonces.get(addr) } From 5a0e2a41e68d41525bc7c8a87e787c09d4b69d7a Mon Sep 17 00:00:00 2001 From: dbadoy4874 Date: Fri, 26 Aug 2022 00:36:05 +0900 Subject: [PATCH 2/5] core: rename nonce cache flag --- core/tx_noncer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/tx_noncer.go b/core/tx_noncer.go index 575fc6942f23..c32ade379aa1 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -24,7 +24,7 @@ import ( lru "github.com/hashicorp/golang-lru" ) -const nonceCacheSize = 1024 * 50 +const nonceCacheLimit = 1024 * 50 // txNoncer is a LRU cache to manage the executable nonces of // accounts in the pool, falling back to reading from a real state database if @@ -37,7 +37,7 @@ type txNoncer struct { // newTxNoncer creates a new LRU cache to track the pool nonces. func newTxNoncer(statedb *state.StateDB) *txNoncer { - cache, _ := lru.New(nonceCacheSize) + cache, _ := lru.New(nonceCacheLimit) return &txNoncer{ fallback: statedb.Copy(), nonces: cache, From 65a7b1fe916ca03c6aecce913e9b2bfa1bd1d819 Mon Sep 17 00:00:00 2001 From: dbadoy4874 Date: Wed, 7 Sep 2022 11:01:54 +0900 Subject: [PATCH 3/5] remove txNoncer test script, mutex in txNoncer --- core/tx_noncer.go | 14 +--- core/tx_noncer_test.go | 145 ----------------------------------------- 2 files changed, 1 insertion(+), 158 deletions(-) delete mode 100644 core/tx_noncer_test.go diff --git a/core/tx_noncer.go b/core/tx_noncer.go index c32ade379aa1..5ce07fd0eebd 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -17,8 +17,6 @@ package core import ( - "sync" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" lru "github.com/hashicorp/golang-lru" @@ -32,7 +30,6 @@ const nonceCacheLimit = 1024 * 50 type txNoncer struct { fallback *state.StateDB nonces *lru.Cache - lock sync.Mutex } // newTxNoncer creates a new LRU cache to track the pool nonces. @@ -50,9 +47,7 @@ func (txn *txNoncer) get(addr common.Address) uint64 { // We use mutex for get operation is the underlying // state will mutate db even for read access. if _, ok := txn.nonces.Get(addr); !ok { - txn.lock.Lock() txn.nonces.Add(addr, txn.fallback.GetNonce(addr)) - txn.lock.Unlock() } nonce, _ := txn.nonces.Get(addr) return nonce.(uint64) @@ -67,14 +62,7 @@ func (txn *txNoncer) set(addr common.Address, nonce uint64) { // setIfLower updates a new virtual nonce into the LRU cache if the // the new one is lower. func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) { - if _, ok := txn.nonces.Get(addr); !ok { - txn.lock.Lock() - txn.nonces.Add(addr, txn.fallback.GetNonce(addr)) - txn.lock.Unlock() - } - - cachedNonce, _ := txn.nonces.Get(addr) - if cachedNonce.(uint64) <= nonce { + if txn.get(addr) <= nonce { return } txn.nonces.Add(addr, nonce) diff --git a/core/tx_noncer_test.go b/core/tx_noncer_test.go deleted file mode 100644 index e4a7db2cf31d..000000000000 --- a/core/tx_noncer_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package core - -import ( - "fmt" - "math/rand" - "runtime" - "sync" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - lru "github.com/hashicorp/golang-lru" -) - -type txNoncerMap struct { - fallback *state.StateDB - nonces map[common.Address]uint64 - lock sync.Mutex -} - -func (txn *txNoncerMap) get(addr common.Address) uint64 { - txn.lock.Lock() - defer txn.lock.Unlock() - - if _, ok := txn.nonces[addr]; !ok { - txn.nonces[addr] = txn.fallback.GetNonce(addr) - } - return txn.nonces[addr] -} - -func (txn *txNoncerMap) set(addr common.Address, nonce uint64) { - txn.lock.Lock() - defer txn.lock.Unlock() - - txn.nonces[addr] = nonce -} - -type txNoncerLRU struct { - fallback *state.StateDB - nonces *lru.Cache -} - -func newTxNoncerMap(statedb *state.StateDB) *txNoncerMap { - return &txNoncerMap{ - fallback: statedb.Copy(), - nonces: make(map[common.Address]uint64), - } -} - -func newTxNoncerLRU(statedb *state.StateDB) *txNoncerLRU { - // lru cache size 1024 * 50 allocated 10 ~ 20 MB - cache, _ := lru.New(1024 * 50) - return &txNoncerLRU{ - fallback: statedb.Copy(), - nonces: cache, - } -} - -func TestTxNoncerMap(t *testing.T) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - txNoncer := newTxNoncerMap(statedb) - - var m runtime.MemStats - runtime.GC() - for i := 0; i < 10000000; i++ { - var b [20]byte - rand.Read(b[:]) - addr := common.Address(b) - txNoncer.set(addr, uint64(0)) - } - runtime.ReadMemStats(&m) - fmt.Printf("Object memory: %.3f MB current\n", float64(m.Alloc)/1024/1024) - fmt.Printf("System memory: %.3f MB current\n", float64(m.Sys)/1024/1024) - fmt.Printf("Allocations: %.3f million\n", float64(m.Mallocs)/1000000) -} - -func TestTxNoncerLRU(t *testing.T) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - txNoncer := newTxNoncerLRU(statedb) - - var m runtime.MemStats - runtime.GC() - for i := 0; i < 10000000; i++ { - var b [20]byte - rand.Read(b[:]) - addr := common.Address(b) - txNoncer.nonces.Add(addr, uint64(1)) - } - runtime.ReadMemStats(&m) - fmt.Printf("Object memory: %.3f MB current\n", float64(m.Alloc)/1024/1024) - fmt.Printf("System memory: %.3f MB current\n", float64(m.Sys)/1024/1024) - fmt.Printf("Allocations: %.3f million\n", float64(m.Mallocs)/1000000) -} - -func BenchmarkMapStore(b *testing.B) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - txNoncer := newTxNoncerMap(statedb) - for i := 0; i < b.N; i++ { - var byt [20]byte - rand.Read(byt[:]) - addr := common.Address(byt) - txNoncer.set(addr, uint64(1)) - } -} - -func BenchmarkLRUStore(b *testing.B) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - txNoncer := newTxNoncerLRU(statedb) - for i := 0; i < b.N; i++ { - var byt [20]byte - rand.Read(byt[:]) - addr := common.Address(byt) - txNoncer.nonces.Add(addr, uint64(1)) - } -} - -func BenchmarkMapLoad(b *testing.B) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - txNoncer := newTxNoncerMap(statedb) - var byt [20]byte - rand.Read(byt[:]) - addr := common.Address(byt) - txNoncer.set(addr, uint64(1)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _ = txNoncer.get(addr) - } -} - -func BenchmarkLRULoad(b *testing.B) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) - txNoncer := newTxNoncerLRU(statedb) - var byt [20]byte - rand.Read(byt[:]) - addr := common.Address(byt) - txNoncer.nonces.Add(addr, uint64(1)) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - v, _ := txNoncer.nonces.Get(addr) - _ = v.(uint64) - } -} From ad8f433e1ba8b6a8f21b694162dd8cb79a4bb2aa Mon Sep 17 00:00:00 2001 From: dbadoy4874 Date: Thu, 8 Sep 2022 23:32:57 +0900 Subject: [PATCH 4/5] revert LRU cache --- core/tx_noncer.go | 53 ++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/core/tx_noncer.go b/core/tx_noncer.go index 5ce07fd0eebd..51fa8cf5a1f9 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -17,27 +17,26 @@ package core import ( + "sync" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" - lru "github.com/hashicorp/golang-lru" ) -const nonceCacheLimit = 1024 * 50 - -// txNoncer is a LRU cache to manage the executable nonces of +// txNoncer is a tiny virtual state database to manage the executable nonces of // accounts in the pool, falling back to reading from a real state database if // an account is unknown. type txNoncer struct { fallback *state.StateDB - nonces *lru.Cache + nonces map[common.Address]uint64 + lock sync.Mutex } -// newTxNoncer creates a new LRU cache to track the pool nonces. +// newTxNoncer creates a new virtual state database to track the pool nonces. func newTxNoncer(statedb *state.StateDB) *txNoncer { - cache, _ := lru.New(nonceCacheLimit) return &txNoncer{ fallback: statedb.Copy(), - nonces: cache, + nonces: make(map[common.Address]uint64), } } @@ -46,31 +45,43 @@ func newTxNoncer(statedb *state.StateDB) *txNoncer { func (txn *txNoncer) get(addr common.Address) uint64 { // We use mutex for get operation is the underlying // state will mutate db even for read access. - if _, ok := txn.nonces.Get(addr); !ok { - txn.nonces.Add(addr, txn.fallback.GetNonce(addr)) + txn.lock.Lock() + defer txn.lock.Unlock() + + if _, ok := txn.nonces[addr]; !ok { + txn.nonces[addr] = txn.fallback.GetNonce(addr) } - nonce, _ := txn.nonces.Get(addr) - return nonce.(uint64) + return txn.nonces[addr] } -// set inserts a new virtual nonce into the LRU cache to be returned +// set inserts a new virtual nonce into the virtual state database to be returned // whenever the pool requests it instead of reaching into the real state database. func (txn *txNoncer) set(addr common.Address, nonce uint64) { - txn.nonces.Add(addr, nonce) + txn.lock.Lock() + defer txn.lock.Unlock() + + txn.nonces[addr] = nonce } -// setIfLower updates a new virtual nonce into the LRU cache if the -// the new one is lower. +// setIfLower updates a new virtual nonce into the virtual state database if the +// new one is lower. func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) { - if txn.get(addr) <= nonce { + txn.lock.Lock() + defer txn.lock.Unlock() + + if _, ok := txn.nonces[addr]; !ok { + txn.nonces[addr] = txn.fallback.GetNonce(addr) + } + if txn.nonces[addr] <= nonce { return } - txn.nonces.Add(addr, nonce) + txn.nonces[addr] = nonce } // setAll sets the nonces for all accounts to the given map. func (txn *txNoncer) setAll(all map[common.Address]uint64) { - for addr, nonce := range all { - txn.nonces.Add(addr, nonce) - } + txn.lock.Lock() + defer txn.lock.Unlock() + + txn.nonces = all } From 36a169c377ac08953d9ecda94e088cbd33bcc1db Mon Sep 17 00:00:00 2001 From: dbadoy4874 Date: Thu, 8 Sep 2022 23:38:46 +0900 Subject: [PATCH 5/5] Do not save when the target address nonce is zero --- core/tx_noncer.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/tx_noncer.go b/core/tx_noncer.go index 51fa8cf5a1f9..257beffa06c6 100644 --- a/core/tx_noncer.go +++ b/core/tx_noncer.go @@ -49,7 +49,9 @@ func (txn *txNoncer) get(addr common.Address) uint64 { defer txn.lock.Unlock() if _, ok := txn.nonces[addr]; !ok { - txn.nonces[addr] = txn.fallback.GetNonce(addr) + if nonce := txn.fallback.GetNonce(addr); nonce != 0 { + txn.nonces[addr] = nonce + } } return txn.nonces[addr] } @@ -70,7 +72,9 @@ func (txn *txNoncer) setIfLower(addr common.Address, nonce uint64) { defer txn.lock.Unlock() if _, ok := txn.nonces[addr]; !ok { - txn.nonces[addr] = txn.fallback.GetNonce(addr) + if nonce := txn.fallback.GetNonce(addr); nonce != 0 { + txn.nonces[addr] = nonce + } } if txn.nonces[addr] <= nonce { return