Skip to content

core: miner: reduce allocations in block building#33375

Merged
MariusVanDerWijden merged 9 commits intoethereum:masterfrom
MariusVanDerWijden:miner-allocs
Feb 3, 2026
Merged

core: miner: reduce allocations in block building#33375
MariusVanDerWijden merged 9 commits intoethereum:masterfrom
MariusVanDerWijden:miner-allocs

Conversation

@MariusVanDerWijden
Copy link
Copy Markdown
Member

@MariusVanDerWijden MariusVanDerWijden commented Dec 8, 2025

I recently went on a longer flight and started profiling the geth block production pipeline.
This PR contains a bunch of individual fixes split into separate commits.
I can drop some if necessary.

Benchmarking is not super easy, the benchmark I wrote is a bit non-deterministic.
I will try to write a better benchmark later

goos: linux
goarch: amd64
pkg: github.com/ethereum/go-ethereum/miner
cpu: Intel(R) Core(TM) Ultra 7 155U
                │ /tmp/old.txt │          /tmp/new.txt          │
                │    sec/op    │   sec/op     vs base           │
BuildPayload-14    141.5µ ± 3%   146.0µ ± 6%  ~ (p=0.346 n=200)

                │ /tmp/old.txt │             /tmp/new.txt             │
                │     B/op     │     B/op      vs base                │
BuildPayload-14   188.2Ki ± 4%   177.4Ki ± 4%  -5.71% (p=0.018 n=200)

                │ /tmp/old.txt │            /tmp/new.txt             │
                │  allocs/op   │  allocs/op   vs base                │
BuildPayload-14    2.703k ± 4%   2.453k ± 5%  -9.25% (p=0.000 n=200)

@MariusVanDerWijden MariusVanDerWijden marked this pull request as ready for review January 7, 2026 11:15
Comment thread core/state/state_object.go
// are replay-protected as well as unprotected homestead transactions.
// Deprecated: always use the Signer interface type
type EIP155Signer struct {
chainId, chainIdMul *big.Int
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still use the 155 signer for recovering the sender of legacy transactions right?

Did you observe significant allocation comes from here?

Copy link
Copy Markdown
Member Author

@MariusVanDerWijden MariusVanDerWijden Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes we still use it and every time we instantiate any signer, we get this allocation

BenchmarkSigner-14    	11503028	       100.0 ns/op	     144 B/op	       4 allocs/op
BenchmarkSigner-14    	21896109	        46.88 ns/op	      64 B/op	       2 allocs/op 
func BenchmarkSigner(b *testing.B) {
	id := big.NewInt(1)
	for range b.N {
		NewPragueSigner(id)
	}
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ROUTINE ======================== github.com/ethereum/go-ethereum/core/types.NewEIP155Signer in github.com/ethereum/go-ethereum/core/types/transaction_signing.go
 240049308  480632052 (flat, cum) 0.061% of Total
         .          .    333:	chainId, chainIdMul *big.Int
         .          .    334:}
         .          .    335:
         .          .    336:func NewEIP155Signer(chainId *big.Int) EIP155Signer {
         .          .    337:	if chainId == nil {
         .          .    338:		chainId = new(big.Int)
 240049308  480632052    339:	}
         .          .    340:	return EIP155Signer{
         .          .    341:		chainId:    chainId,
         .          .    342:		chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
         .          .    343:	}
         .          .    344:}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it's achieved with the cost of sender recovery right? I feel there are still a tons of legacy transactions in the network.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, its not much slower iirc. Also the last number I have in my head for legacy type tx was something like 10% of the network. Let me quickly write a benchmark

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

func BenchmarkRecoverSig(b *testing.B) {
	key, _ := defaultTestKey()
	tx := NewTransaction(0, common.Address{}, new(big.Int), 0, new(big.Int), nil)
	tx, err := SignTx(tx, NewEIP155Signer(big.NewInt(1)), key)
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for b.Loop() {
		_, err := Sender(NewPragueSigner(big.NewInt(1)), tx)
		if err != nil {
			b.Error(err)
		}
	}
}

// BenchmarkRecoverSig-14    	 7953165	       151.8 ns/op	     184 B/op	       6 allocs/op
// BenchmarkRecoverSig-14    	17003580	        70.78 ns/op	      88 B/op	       3 allocs/op

Looks like its much faster if you factor in the Signer creation

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The slowdown if you don't factor in the signer creation is also pretty negligble imho:


// BenchmarkRecoverSig-14    	172942711	         6.947 ns/op	       0 B/op	       0 allocs/op
// BenchmarkRecoverSig-14    	164950395	         7.335 ns/op	       0 B/op	       0 allocs/op
``

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

goos: darwin
goarch: arm64
pkg: github.com/ethereum/go-ethereum/core/types
cpu: Apple M1 Pro
BenchmarkBigMul
BenchmarkBigMul-8   	22464758	        52.80 ns/op

func BenchmarkBigMul(b *testing.B) {
	id := big.NewInt(1)
	for b.Loop() {
		x := new(big.Int).Mul(id, big.NewInt(2))
		_ = x
	}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add/Sub is much faster than mul

Comment thread core/vm/evm.go
Comment thread core/vm/instructions.go
}

func opGasprice(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
v, _ := uint256.FromBig(evm.GasPrice)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you think Clone is cheaper than FromBig?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No using a uint256 is better in the evm than a big int and having to convert everytime we use it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// BenchmarkOpGasprice-14    	145359624	         8.234 ns/op	       0 B/op	       0 allocs/op
// BenchmarkOpGasprice-14    	250398700	         4.838 ns/op	       0 B/op	       0 allocs/op
func BenchmarkOpGasprice(b *testing.B) {
	b.ReportAllocs()
	evm := NewEVM(BlockContext{}, nil, params.TestChainConfig, Config{})
	evm.GasPrice = big.NewInt(1_000_000_000)
	scope := &ScopeContext{
		Stack: newstack(),
	}
	pc := uint64(0)
	for i := 0; i < b.N; i++ {
		scope.Stack.data = scope.Stack.data[:0]
		if _, err := opGasprice(&pc, evm, scope); err != nil {
			b.Fatalf("opGasprice error: %v", err)
		}
	}

}

Comment thread core/state/journal.go
revert(*StateDB)

// dirtied returns the Ethereum address modified by this journal entry.
dirtied() *common.Address
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will it introdice a noticeable impact on the allocation? Just out of curiosity.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ROUTINE ======================== github.com/ethereum/go-ethereum/core/state.(*journal).append in github.com/ethereum/go-ethereum/core/state/journal.go
3735047495 3735047495 (flat, cum)  0.48% of Total
         .          .    101:func (j *journal) append(entry journalEntry) {
 594360687  594360687    102:	j.entries = append(j.entries, entry)
3005545312 3005545312    103:	if addr := entry.dirtied(); addr != nil {
 135141496  135141496    104:		j.dirties[*addr]++
         .          .    105:	}

Almost half a percent, but its also in the critical path, so it might make a small impact

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure allocation can be reduced by adopting the new approach? The address object is returned as the copy alongside the boolean flag.

Copy link
Copy Markdown
Contributor

@jrhea jrhea Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escape analysis shows "moved to heap: ch" in journal.go on master for lines 297/328/343/357/372/387/402 due to return &ch.account from dirtied() *Address. Those escapes go away with (Address,bool).

$ go test ./core/state -run TestNonExistent -gcflags='-m=2' 2>&1 | rg 'journal\.go' | rg 'moved to heap: ch|escapes to heap: ch'
core/state/journal.go:297:7: moved to heap: ch
core/state/journal.go:328:7: moved to heap: ch
core/state/journal.go:343:7: moved to heap: ch
core/state/journal.go:357:7: moved to heap: ch
core/state/journal.go:372:7: moved to heap: ch
core/state/journal.go:387:7: moved to heap: ch
core/state/journal.go:402:7: moved to heap: ch

@MariusVanDerWijden MariusVanDerWijden merged commit 16a6531 into ethereum:master Feb 3, 2026
7 of 8 checks passed
@MariusVanDerWijden MariusVanDerWijden deleted the miner-allocs branch February 3, 2026 07:19
@s1na s1na added this to the 1.17.0 milestone Feb 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants