Skip to content

Commit 0db99f4

Browse files
core/types: reduce allocations in tx.EffectiveGasTip (ethereum#31598)
This PR introduces an allocation-free version of the Transaction.EffectiveGasTip method to improve performance by reducing memory allocations. ## Changes - Added a new `EffectiveGasTipInto` method that accepts a destination parameter to avoid memory allocations - Refactored the existing `EffectiveGasTip` method to use the new allocation-free implementation - Updated related methods (`EffectiveGasTipValue`, `EffectiveGasTipCmp`, `EffectiveGasTipIntCmp`) to use the allocation-free approach - Added tests and benchmarks to verify correctness and measure performance improvements ## Motivation In high-transaction-volume environments, the `EffectiveGasTip` method is called frequently. Reducing memory allocations in this method decreases garbage collection pressure and improves overall system performance. ## Benchmark Results As-Is BenchmarkEffectiveGasTip/Original-10 42089140 27.45 ns/op 8 B/op 1 allocs/op To-Be BenchmarkEffectiveGasTip/IntoMethod-10 72353263 16.73 ns/op 0 B/op 0 allocs/op ## Summary of Improvements - **Performance**: ~39% faster execution (27.45 ns/op → 16.73 ns/op) - **Memory**: Eliminated all allocations (8 B/op → 0 B/op) - **Allocation count**: Reduced from 1 to 0 allocations per operation This optimization follows the same pattern successfully applied to other methods in the codebase, maintaining API compatibility while improving performance. ## Safety & Compatibility This optimization has no side effects or adverse impacts because: - It maintains functional equivalence as confirmed by comprehensive tests - It preserves API compatibility with existing callers - It follows clear memory ownership patterns with the destination parameter - It maintains thread safety by only modifying the caller-provided destination parameter This optimization follows the same pattern successfully applied to other methods in the codebase, providing better performance without compromising stability or correctness. --------- Co-authored-by: lightclient <[email protected]>
1 parent 0eb2eee commit 0db99f4

File tree

3 files changed

+126
-17
lines changed

3 files changed

+126
-17
lines changed

core/types/transaction.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -355,44 +355,53 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
355355
// Note: if the effective gasTipCap is negative, this method returns both error
356356
// the actual negative value, _and_ ErrGasFeeCapTooLow
357357
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
358+
dst := new(big.Int)
359+
err := tx.calcEffectiveGasTip(dst, baseFee)
360+
return dst, err
361+
}
362+
363+
// calcEffectiveGasTip calculates the effective gas tip of the transaction and
364+
// saves the result to dst.
365+
func (tx *Transaction) calcEffectiveGasTip(dst *big.Int, baseFee *big.Int) error {
358366
if baseFee == nil {
359-
return tx.GasTipCap(), nil
367+
dst.Set(tx.inner.gasTipCap())
368+
return nil
360369
}
370+
361371
var err error
362-
gasFeeCap := tx.GasFeeCap()
372+
gasFeeCap := tx.inner.gasFeeCap()
363373
if gasFeeCap.Cmp(baseFee) < 0 {
364374
err = ErrGasFeeCapTooLow
365375
}
366-
gasFeeCap = gasFeeCap.Sub(gasFeeCap, baseFee)
367376

368-
gasTipCap := tx.GasTipCap()
369-
if gasTipCap.Cmp(gasFeeCap) < 0 {
370-
return gasTipCap, err
377+
dst.Sub(gasFeeCap, baseFee)
378+
gasTipCap := tx.inner.gasTipCap()
379+
if gasTipCap.Cmp(dst) < 0 {
380+
dst.Set(gasTipCap)
371381
}
372-
return gasFeeCap, err
373-
}
374-
375-
// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an
376-
// error in case the effective gasTipCap is negative
377-
func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int {
378-
effectiveTip, _ := tx.EffectiveGasTip(baseFee)
379-
return effectiveTip
382+
return err
380383
}
381384

382385
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
383386
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
384387
if baseFee == nil {
385388
return tx.GasTipCapCmp(other)
386389
}
387-
return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee))
390+
// Use more efficient internal method.
391+
txTip, otherTip := new(big.Int), new(big.Int)
392+
tx.calcEffectiveGasTip(txTip, baseFee)
393+
other.calcEffectiveGasTip(otherTip, baseFee)
394+
return txTip.Cmp(otherTip)
388395
}
389396

390397
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
391398
func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int {
392399
if baseFee == nil {
393400
return tx.GasTipCapIntCmp(other)
394401
}
395-
return tx.EffectiveGasTipValue(baseFee).Cmp(other)
402+
txTip := new(big.Int)
403+
tx.calcEffectiveGasTip(txTip, baseFee)
404+
return txTip.Cmp(other)
396405
}
397406

398407
// BlobGas returns the blob gas limit of the transaction for blob transactions, 0 otherwise.

core/types/transaction_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
"github.com/ethereum/go-ethereum/common"
3131
"github.com/ethereum/go-ethereum/crypto"
32+
"github.com/ethereum/go-ethereum/params"
3233
"github.com/ethereum/go-ethereum/rlp"
3334
)
3435

@@ -593,3 +594,101 @@ func BenchmarkHash(b *testing.B) {
593594
signer.Hash(tx)
594595
}
595596
}
597+
598+
func BenchmarkEffectiveGasTip(b *testing.B) {
599+
signer := LatestSigner(params.TestChainConfig)
600+
key, _ := crypto.GenerateKey()
601+
txdata := &DynamicFeeTx{
602+
ChainID: big.NewInt(1),
603+
Nonce: 0,
604+
GasTipCap: big.NewInt(2000000000),
605+
GasFeeCap: big.NewInt(3000000000),
606+
Gas: 21000,
607+
To: &common.Address{},
608+
Value: big.NewInt(0),
609+
Data: nil,
610+
}
611+
tx, _ := SignNewTx(key, signer, txdata)
612+
baseFee := big.NewInt(1000000000) // 1 gwei
613+
614+
b.Run("Original", func(b *testing.B) {
615+
b.ReportAllocs()
616+
for i := 0; i < b.N; i++ {
617+
_, err := tx.EffectiveGasTip(baseFee)
618+
if err != nil {
619+
b.Fatal(err)
620+
}
621+
}
622+
})
623+
624+
b.Run("IntoMethod", func(b *testing.B) {
625+
b.ReportAllocs()
626+
dst := new(big.Int)
627+
for i := 0; i < b.N; i++ {
628+
err := tx.calcEffectiveGasTip(dst, baseFee)
629+
if err != nil {
630+
b.Fatal(err)
631+
}
632+
}
633+
})
634+
}
635+
636+
func TestEffectiveGasTipInto(t *testing.T) {
637+
signer := LatestSigner(params.TestChainConfig)
638+
key, _ := crypto.GenerateKey()
639+
640+
testCases := []struct {
641+
tipCap int64
642+
feeCap int64
643+
baseFee *int64
644+
}{
645+
{tipCap: 1, feeCap: 100, baseFee: intPtr(50)},
646+
{tipCap: 10, feeCap: 100, baseFee: intPtr(50)},
647+
{tipCap: 50, feeCap: 100, baseFee: intPtr(50)},
648+
{tipCap: 100, feeCap: 100, baseFee: intPtr(50)},
649+
{tipCap: 1, feeCap: 50, baseFee: intPtr(50)},
650+
{tipCap: 1, feeCap: 20, baseFee: intPtr(50)}, // Base fee higher than fee cap
651+
{tipCap: 50, feeCap: 100, baseFee: intPtr(0)},
652+
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
653+
}
654+
655+
for i, tc := range testCases {
656+
txdata := &DynamicFeeTx{
657+
ChainID: big.NewInt(1),
658+
Nonce: 0,
659+
GasTipCap: big.NewInt(tc.tipCap),
660+
GasFeeCap: big.NewInt(tc.feeCap),
661+
Gas: 21000,
662+
To: &common.Address{},
663+
Value: big.NewInt(0),
664+
Data: nil,
665+
}
666+
tx, _ := SignNewTx(key, signer, txdata)
667+
668+
var baseFee *big.Int
669+
if tc.baseFee != nil {
670+
baseFee = big.NewInt(*tc.baseFee)
671+
}
672+
673+
// Get result from original method
674+
orig, origErr := tx.EffectiveGasTip(baseFee)
675+
676+
// Get result from new method
677+
dst := new(big.Int)
678+
newErr := tx.calcEffectiveGasTip(dst, baseFee)
679+
680+
// Compare results
681+
if (origErr != nil) != (newErr != nil) {
682+
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
683+
}
684+
685+
if orig.Cmp(dst) != 0 {
686+
t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst)
687+
}
688+
}
689+
}
690+
691+
// Helper function to create integer pointer
692+
func intPtr(i int64) *int64 {
693+
return &i
694+
}

eth/tracers/js/goja.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from
260260
t.activePrecompiles = vm.ActivePrecompiles(rules)
261261
t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64())
262262
t.ctx["gas"] = t.vm.ToValue(tx.Gas())
263-
gasPriceBig, err := t.toBig(t.vm, tx.EffectiveGasTipValue(env.BaseFee).String())
263+
gasTip, _ := tx.EffectiveGasTip(env.BaseFee)
264+
gasPriceBig, err := t.toBig(t.vm, gasTip.String())
264265
if err != nil {
265266
t.err = err
266267
return

0 commit comments

Comments
 (0)