mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-04 22:18:40 +00:00
core/vm: global cache for jumpdest bitmaps (#34850)
``` ● Global JUMPDEST Cache - engine_newPayload benchmark ============================================================ Commit before:a06558042(master) Commit after:faef2454f(core/vm: global cache for jumpdest bitmaps) Blocks: 1k mainnet (24950066 → 24951065) Runs: 3 each, clean ZFS clone per run Before (avg) With Falcon (avg) Δ Throughput 176.0 MGas/s 190.7 MGas/s +8.3% Mean NP 172.3ms 159.0ms -7.7% p50 162.8ms 150.7ms -7.4% p95 282.4ms 259.8ms -8.0% p99 391.0ms 371.6ms -5.0% Machine: Intel Ultra 7 255H, 62GB DDR5, NVMe (ZFS), governor=performance, turbo=off ``` --------- Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
parent
622cef2d06
commit
d902837256
8 changed files with 93 additions and 9 deletions
|
|
@ -330,6 +330,7 @@ type BlockChain struct {
|
||||||
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
|
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
|
||||||
triedb *triedb.Database // The database handler for maintaining trie nodes.
|
triedb *triedb.Database // The database handler for maintaining trie nodes.
|
||||||
codedb *state.CodeDB // The database handler for maintaining contract codes.
|
codedb *state.CodeDB // The database handler for maintaining contract codes.
|
||||||
|
jumpDestCache vm.JumpDestCache // Shared JUMPDEST analysis cache for block processing
|
||||||
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
|
||||||
|
|
||||||
hc *HeaderChain
|
hc *HeaderChain
|
||||||
|
|
@ -412,6 +413,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
|
||||||
db: db,
|
db: db,
|
||||||
triedb: triedb,
|
triedb: triedb,
|
||||||
codedb: state.NewCodeDB(db),
|
codedb: state.NewCodeDB(db),
|
||||||
|
jumpDestCache: NewJumpDestCache(),
|
||||||
triegc: prque.New[int64, common.Hash](nil),
|
triegc: prque.New[int64, common.Hash](nil),
|
||||||
chainmu: syncx.NewClosableMutex(),
|
chainmu: syncx.NewClosableMutex(),
|
||||||
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
|
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
|
||||||
|
|
@ -2183,7 +2185,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
// Disable tracing for prefetcher executions.
|
// Disable tracing for prefetcher executions.
|
||||||
vmCfg := bc.cfg.VmConfig
|
vmCfg := bc.cfg.VmConfig
|
||||||
vmCfg.Tracer = nil
|
vmCfg.Tracer = nil
|
||||||
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
|
bc.prefetcher.Prefetch(block, throwaway, bc.jumpDestCache, vmCfg, &interrupt)
|
||||||
|
|
||||||
blockPrefetchExecuteTimer.Update(time.Since(start))
|
blockPrefetchExecuteTimer.Update(time.Since(start))
|
||||||
if interrupt.Load() {
|
if interrupt.Load() {
|
||||||
|
|
@ -2229,7 +2231,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
// Process block using the parent state as reference point
|
// Process block using the parent state as reference point
|
||||||
pstart := time.Now()
|
pstart := time.Now()
|
||||||
pctx, _, spanEnd := telemetry.StartSpan(ctx, "bc.processor.Process")
|
pctx, _, spanEnd := telemetry.StartSpan(ctx, "bc.processor.Process")
|
||||||
res, err := bc.processor.Process(pctx, block, statedb, bc.cfg.VmConfig)
|
res, err := bc.processor.Process(pctx, block, statedb, bc.jumpDestCache, bc.cfg.VmConfig)
|
||||||
spanEnd(&err)
|
spanEnd(&err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
bc.reportBadBlock(block, res, err)
|
bc.reportBadBlock(block, res, err)
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
res, err := blockchain.processor.Process(context.Background(), block, statedb, vm.Config{})
|
res, err := blockchain.processor.Process(context.Background(), block, statedb, nil, vm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
blockchain.reportBadBlock(block, res, err)
|
blockchain.reportBadBlock(block, res, err)
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
76
core/jumpdest.go
Normal file
76
core/jumpdest.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright 2026 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 core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/lru"
|
||||||
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
jumpDestHitMeter = metrics.NewRegisteredMeter("chain/cache/jumpdest/hit", nil)
|
||||||
|
jumpDestMissMeter = metrics.NewRegisteredMeter("chain/cache/jumpdest/miss", nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// jumpDestBuckets is the number of independent LRU shards. Code hashes
|
||||||
|
// are dispatched by the low bits of the first byte to spread load across
|
||||||
|
// shards and reduce mutex contention from the parallel prefetcher.
|
||||||
|
jumpDestBuckets = 8
|
||||||
|
|
||||||
|
// jumpDestBucketSize is the per-shard byte budget.
|
||||||
|
jumpDestBucketSize = 8 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// shardedJumpDestCache is a thread-safe, byte-bounded LRU of JUMPDEST analysis
|
||||||
|
// bitmaps, sharded into independent buckets to reduce lock contention. It is
|
||||||
|
// owned by BlockChain and shared across block processing and prefetching,
|
||||||
|
// keyed by the immutable contract code hash.
|
||||||
|
type shardedJumpDestCache struct {
|
||||||
|
buckets [jumpDestBuckets]struct {
|
||||||
|
dest *lru.SizeConstrainedCache[common.Hash, vm.BitVec]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJumpDestCache constructs the analysis cache.
|
||||||
|
func NewJumpDestCache() vm.JumpDestCache {
|
||||||
|
c := new(shardedJumpDestCache)
|
||||||
|
for i := range c.buckets {
|
||||||
|
c.buckets[i].dest = lru.NewSizeConstrainedCache[common.Hash, vm.BitVec](jumpDestBucketSize)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load retrieves the cached jumpdest analysis for the given code hash.
|
||||||
|
func (c *shardedJumpDestCache) Load(hash common.Hash) (vm.BitVec, bool) {
|
||||||
|
bucket := &c.buckets[hash[0]&(jumpDestBuckets-1)]
|
||||||
|
v, ok := bucket.dest.Get(hash)
|
||||||
|
if ok {
|
||||||
|
jumpDestHitMeter.Mark(1)
|
||||||
|
} else {
|
||||||
|
jumpDestMissMeter.Mark(1)
|
||||||
|
}
|
||||||
|
return v, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store saves the jumpdest analysis for the given code hash.
|
||||||
|
func (c *shardedJumpDestCache) Store(hash common.Hash, b vm.BitVec) {
|
||||||
|
bucket := &c.buckets[hash[0]&(jumpDestBuckets-1)]
|
||||||
|
bucket.dest.Add(hash, b)
|
||||||
|
}
|
||||||
|
|
@ -49,7 +49,7 @@ func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePr
|
||||||
// Prefetch processes the state changes according to the Ethereum rules by running
|
// Prefetch processes the state changes according to the Ethereum rules by running
|
||||||
// the transaction messages using the statedb, but any changes are discarded. The
|
// the transaction messages using the statedb, but any changes are discarded. The
|
||||||
// only goal is to warm the state caches.
|
// only goal is to warm the state caches.
|
||||||
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) {
|
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config, interrupt *atomic.Bool) {
|
||||||
var (
|
var (
|
||||||
fails atomic.Int64
|
fails atomic.Int64
|
||||||
header = block.Header()
|
header = block.Header()
|
||||||
|
|
@ -94,6 +94,9 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
|
||||||
// Execute the message to preload the implicit touched states
|
// Execute the message to preload the implicit touched states
|
||||||
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
|
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
|
||||||
defer evm.Release()
|
defer evm.Release()
|
||||||
|
if jumpDestCache != nil {
|
||||||
|
evm.SetJumpDestCache(jumpDestCache)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert the transaction into an executable message and pre-cache its sender
|
// Convert the transaction into an executable message and pre-cache its sender
|
||||||
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
msg, err := TransactionToMessage(tx, signer, header.BaseFee)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig {
|
||||||
// Process returns the receipts and logs accumulated during the process and
|
// Process returns the receipts and logs accumulated during the process and
|
||||||
// returns the amount of gas that was used in the process. If any of the
|
// returns the amount of gas that was used in the process. If any of the
|
||||||
// transactions failed to execute due to insufficient gas it will return an error.
|
// transactions failed to execute due to insufficient gas it will return an error.
|
||||||
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
|
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config) (*ProcessResult, error) {
|
||||||
var (
|
var (
|
||||||
config = p.chainConfig()
|
config = p.chainConfig()
|
||||||
receipts = make(types.Receipts, 0, len(block.Transactions()))
|
receipts = make(types.Receipts, 0, len(block.Transactions()))
|
||||||
|
|
@ -88,6 +88,9 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
||||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||||
)
|
)
|
||||||
defer evm.Release()
|
defer evm.Release()
|
||||||
|
if jumpDestCache != nil {
|
||||||
|
evm.SetJumpDestCache(jumpDestCache)
|
||||||
|
}
|
||||||
|
|
||||||
// Run the pre-execution system calls
|
// Run the pre-execution system calls
|
||||||
blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()))
|
blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()))
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig
|
||||||
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
|
||||||
|
|
||||||
// Run the stateless blocks processing and self-validate certain fields
|
// Run the stateless blocks processing and self-validate certain fields
|
||||||
res, err := processor.Process(ctx, block, db, vmconfig)
|
res, err := processor.Process(ctx, block, db, nil, vmconfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Hash{}, common.Hash{}, err
|
return common.Hash{}, common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ type Prefetcher interface {
|
||||||
// Prefetch processes the state changes according to the Ethereum rules by running
|
// Prefetch processes the state changes according to the Ethereum rules by running
|
||||||
// the transaction messages using the statedb, but any changes are discarded. The
|
// the transaction messages using the statedb, but any changes are discarded. The
|
||||||
// only goal is to pre-cache transaction signatures and state trie nodes.
|
// only goal is to pre-cache transaction signatures and state trie nodes.
|
||||||
Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool)
|
Prefetch(block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config, interrupt *atomic.Bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processor is an interface for processing blocks using a given initial state.
|
// Processor is an interface for processing blocks using a given initial state.
|
||||||
|
|
@ -50,7 +50,7 @@ type Processor interface {
|
||||||
// Process processes the state changes according to the Ethereum rules by running
|
// Process processes the state changes according to the Ethereum rules by running
|
||||||
// the transaction messages using the statedb and applying any rewards to both
|
// the transaction messages using the statedb and applying any rewards to both
|
||||||
// the processor (coinbase) and any included uncles.
|
// the processor (coinbase) and any included uncles.
|
||||||
Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
|
Process(ctx context.Context, block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config) (*ProcessResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProcessResult contains the values computed by Process.
|
// ProcessResult contains the values computed by Process.
|
||||||
|
|
|
||||||
|
|
@ -151,7 +151,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, base *st
|
||||||
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
|
||||||
return nil, nil, fmt.Errorf("block #%d not found", next)
|
return nil, nil, fmt.Errorf("block #%d not found", next)
|
||||||
}
|
}
|
||||||
_, err := eth.blockchain.Processor().Process(ctx, current, statedb, vm.Config{})
|
_, err := eth.blockchain.Processor().Process(ctx, current, statedb, nil, vm.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
|
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue