Skip to content

Commit 10421ed

Browse files
core/types: reduce allocations for transaction comparison (#31912)
This PR should reduce overall allocations of a running node by ~10 percent. Since most allocations are coming from the re-heaping of the transaction pool. ``` (pprof) list EffectiveGasTipCmp Total: 38197204475 ROUTINE ======================== github.com/ethereum/go-ethereum/core/types.(*Transaction).EffectiveGasTipCmp in github.com/ethereum/go-ethereum/core/types/transaction.go 0 3766837369 (flat, cum) 9.86% of Total . . 386:func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int { . . 387: if baseFee == nil { . . 388: return tx.GasTipCapCmp(other) . . 389: } . . 390: // Use more efficient internal method. . . 391: txTip, otherTip := new(big.Int), new(big.Int) . 1796172553 392: tx.calcEffectiveGasTip(txTip, baseFee) . 1970664816 393: other.calcEffectiveGasTip(otherTip, baseFee) . . 394: return txTip.Cmp(otherTip) . . 395:} . . 396: . . 397:// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap. . . 398:func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int { ``` This PR reduces the allocations for comparing two transactions from 2 to 0: ``` goos: linux goarch: amd64 pkg: github.com/ethereum/go-ethereum/core/types cpu: Intel(R) Core(TM) Ultra 7 155U │ /tmp/old.txt │ /tmp/new.txt │ │ sec/op │ sec/op vs base │ EffectiveGasTipCmp/Original-14 64.67n ± 2% 25.13n ± 9% -61.13% (p=0.000 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ B/op │ B/op vs base │ EffectiveGasTipCmp/Original-14 16.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) │ /tmp/old.txt │ /tmp/new.txt │ │ allocs/op │ allocs/op vs base │ EffectiveGasTipCmp/Original-14 2.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) ``` It also speeds up the process by ~60% There are two minor caveats with this PR: - We change the API for `EffectiveGasTipCmp` and `EffectiveGasTipIntCmp` (which are probably not used by much) - We slightly change the behavior of `tx.EffectiveGasTip` when it returns an error. It would previously return a negative number on error, now it does not (since uint256 does not allow for negative numbers) --------- Signed-off-by: Csaba Kiraly <csaba.kiraly@gmail.com> Co-authored-by: Csaba Kiraly <csaba.kiraly@gmail.com>
1 parent f3467d1 commit 10421ed

File tree

4 files changed

+95
-46
lines changed

4 files changed

+95
-46
lines changed

core/txpool/legacypool/legacypool.go

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -514,26 +514,15 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
514514
pool.mu.Lock()
515515
defer pool.mu.Unlock()
516516

517-
// Convert the new uint256.Int types to the old big.Int ones used by the legacy pool
518-
var (
519-
minTipBig *big.Int
520-
baseFeeBig *big.Int
521-
)
522-
if filter.MinTip != nil {
523-
minTipBig = filter.MinTip.ToBig()
524-
}
525-
if filter.BaseFee != nil {
526-
baseFeeBig = filter.BaseFee.ToBig()
527-
}
528517
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
529518
for addr, list := range pool.pending {
530519
txs := list.Flatten()
531520

532521
// If the miner requests tip enforcement, cap the lists now
533-
if minTipBig != nil || filter.GasLimitCap != 0 {
522+
if filter.MinTip != nil || filter.GasLimitCap != 0 {
534523
for i, tx := range txs {
535-
if minTipBig != nil {
536-
if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 {
524+
if filter.MinTip != nil {
525+
if tx.EffectiveGasTipIntCmp(filter.MinTip, filter.BaseFee) < 0 {
537526
txs = txs[:i]
538527
break
539528
}

core/txpool/legacypool/list.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ func (l *list) subTotalCost(txs []*types.Transaction) {
475475
// then the heap is sorted based on the effective tip based on the given base fee.
476476
// If baseFee is nil then the sorting is based on gasFeeCap.
477477
type priceHeap struct {
478-
baseFee *big.Int // heap should always be re-sorted after baseFee is changed
478+
baseFee *uint256.Int // heap should always be re-sorted after baseFee is changed
479479
list []*types.Transaction
480480
}
481481

@@ -677,6 +677,10 @@ func (l *pricedList) Reheap() {
677677
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
678678
// necessary to call right before SetBaseFee when processing a new block.
679679
func (l *pricedList) SetBaseFee(baseFee *big.Int) {
680-
l.urgent.baseFee = baseFee
680+
base := new(uint256.Int)
681+
if baseFee != nil {
682+
base.SetFromBig(baseFee)
683+
}
684+
l.urgent.baseFee = base
681685
l.Reheap()
682686
}

core/types/transaction.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"github.com/ethereum/go-ethereum/common"
2929
"github.com/ethereum/go-ethereum/crypto"
3030
"github.com/ethereum/go-ethereum/rlp"
31+
"github.com/holiman/uint256"
3132
)
3233

3334
var (
@@ -36,6 +37,7 @@ var (
3637
ErrInvalidTxType = errors.New("transaction type not valid in this context")
3738
ErrTxTypeNotSupported = errors.New("transaction type not supported")
3839
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
40+
ErrUint256Overflow = errors.New("bigint overflow, too large for uint256")
3941
errShortTypedTx = errors.New("typed transaction too short")
4042
errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
4143
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
@@ -352,54 +354,66 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
352354
}
353355

354356
// EffectiveGasTip returns the effective miner gasTipCap for the given base fee.
355-
// Note: if the effective gasTipCap is negative, this method returns both error
356-
// the actual negative value, _and_ ErrGasFeeCapTooLow
357+
// Note: if the effective gasTipCap would be negative, this method
358+
// returns ErrGasFeeCapTooLow, and value is undefined.
357359
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
360+
dst := new(uint256.Int)
361+
base := new(uint256.Int)
362+
if baseFee != nil {
363+
if base.SetFromBig(baseFee) {
364+
return nil, ErrUint256Overflow
365+
}
366+
}
367+
err := tx.calcEffectiveGasTip(dst, base)
368+
return dst.ToBig(), err
361369
}
362370

363371
// calcEffectiveGasTip calculates the effective gas tip of the transaction and
364372
// saves the result to dst.
365-
func (tx *Transaction) calcEffectiveGasTip(dst *big.Int, baseFee *big.Int) error {
373+
func (tx *Transaction) calcEffectiveGasTip(dst *uint256.Int, baseFee *uint256.Int) error {
366374
if baseFee == nil {
367-
dst.Set(tx.inner.gasTipCap())
375+
if dst.SetFromBig(tx.inner.gasTipCap()) {
376+
return ErrUint256Overflow
377+
}
368378
return nil
369379
}
370380

371381
var err error
372-
gasFeeCap := tx.inner.gasFeeCap()
373-
if gasFeeCap.Cmp(baseFee) < 0 {
382+
if dst.SetFromBig(tx.inner.gasFeeCap()) {
383+
return ErrUint256Overflow
384+
}
385+
if dst.Cmp(baseFee) < 0 {
374386
err = ErrGasFeeCapTooLow
375387
}
376388

377-
dst.Sub(gasFeeCap, baseFee)
378-
gasTipCap := tx.inner.gasTipCap()
389+
dst.Sub(dst, baseFee)
390+
gasTipCap := new(uint256.Int)
391+
if gasTipCap.SetFromBig(tx.inner.gasTipCap()) {
392+
return ErrUint256Overflow
393+
}
379394
if gasTipCap.Cmp(dst) < 0 {
380395
dst.Set(gasTipCap)
381396
}
382397
return err
383398
}
384399

385-
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
386-
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
400+
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int {
387401
if baseFee == nil {
388402
return tx.GasTipCapCmp(other)
389403
}
390404
// Use more efficient internal method.
391-
txTip, otherTip := new(big.Int), new(big.Int)
405+
txTip, otherTip := new(uint256.Int), new(uint256.Int)
392406
tx.calcEffectiveGasTip(txTip, baseFee)
393407
other.calcEffectiveGasTip(otherTip, baseFee)
394408
return txTip.Cmp(otherTip)
395409
}
396410

397411
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
398-
func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int {
412+
func (tx *Transaction) EffectiveGasTipIntCmp(other *uint256.Int, baseFee *uint256.Int) int {
399413
if baseFee == nil {
400-
return tx.GasTipCapIntCmp(other)
414+
return tx.GasTipCapIntCmp(other.ToBig())
401415
}
402-
txTip := new(big.Int)
416+
txTip := new(uint256.Int)
403417
tx.calcEffectiveGasTip(txTip, baseFee)
404418
return txTip.Cmp(other)
405419
}

core/types/transaction_test.go

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/ethereum/go-ethereum/crypto"
3232
"github.com/ethereum/go-ethereum/params"
3333
"github.com/ethereum/go-ethereum/rlp"
34+
"github.com/holiman/uint256"
3435
)
3536

3637
// The values in those tests are from the Transaction Tests
@@ -609,12 +610,12 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
609610
Data: nil,
610611
}
611612
tx, _ := SignNewTx(key, signer, txdata)
612-
baseFee := big.NewInt(1000000000) // 1 gwei
613+
baseFee := uint256.NewInt(1000000000) // 1 gwei
613614

614615
b.Run("Original", func(b *testing.B) {
615616
b.ReportAllocs()
616617
for i := 0; i < b.N; i++ {
617-
_, err := tx.EffectiveGasTip(baseFee)
618+
_, err := tx.EffectiveGasTip(baseFee.ToBig())
618619
if err != nil {
619620
b.Fatal(err)
620621
}
@@ -623,7 +624,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
623624

624625
b.Run("IntoMethod", func(b *testing.B) {
625626
b.ReportAllocs()
626-
dst := new(big.Int)
627+
dst := new(uint256.Int)
627628
for i := 0; i < b.N; i++ {
628629
err := tx.calcEffectiveGasTip(dst, baseFee)
629630
if err != nil {
@@ -634,9 +635,6 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
634635
}
635636

636637
func TestEffectiveGasTipInto(t *testing.T) {
637-
signer := LatestSigner(params.TestChainConfig)
638-
key, _ := crypto.GenerateKey()
639-
640638
testCases := []struct {
641639
tipCap int64
642640
feeCap int64
@@ -652,8 +650,26 @@ func TestEffectiveGasTipInto(t *testing.T) {
652650
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
653651
}
654652

653+
// original, non-allocation golfed version
654+
orig := func(tx *Transaction, baseFee *big.Int) (*big.Int, error) {
655+
if baseFee == nil {
656+
return tx.GasTipCap(), nil
657+
}
658+
var err error
659+
gasFeeCap := tx.GasFeeCap()
660+
if gasFeeCap.Cmp(baseFee) < 0 {
661+
err = ErrGasFeeCapTooLow
662+
}
663+
gasFeeCap = gasFeeCap.Sub(gasFeeCap, baseFee)
664+
gasTipCap := tx.GasTipCap()
665+
if gasTipCap.Cmp(gasFeeCap) < 0 {
666+
return gasTipCap, err
667+
}
668+
return gasFeeCap, err
669+
}
670+
655671
for i, tc := range testCases {
656-
txdata := &DynamicFeeTx{
672+
tx := NewTx(&DynamicFeeTx{
657673
ChainID: big.NewInt(1),
658674
Nonce: 0,
659675
GasTipCap: big.NewInt(tc.tipCap),
@@ -662,27 +678,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
662678
To: &common.Address{},
663679
Value: big.NewInt(0),
664680
Data: nil,
665-
}
666-
tx, _ := SignNewTx(key, signer, txdata)
681+
})
667682

668683
var baseFee *big.Int
684+
var baseFee2 *uint256.Int
669685
if tc.baseFee != nil {
670686
baseFee = big.NewInt(*tc.baseFee)
687+
baseFee2 = uint256.NewInt(uint64(*tc.baseFee))
671688
}
672689

673690
// Get result from original method
674-
orig, origErr := tx.EffectiveGasTip(baseFee)
691+
orig, origErr := orig(tx, baseFee)
675692

676693
// Get result from new method
677-
dst := new(big.Int)
678-
newErr := tx.calcEffectiveGasTip(dst, baseFee)
694+
dst := new(uint256.Int)
695+
newErr := tx.calcEffectiveGasTip(dst, baseFee2)
679696

680697
// Compare results
681698
if (origErr != nil) != (newErr != nil) {
682699
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
683700
}
684701

685-
if orig.Cmp(dst) != 0 {
702+
if origErr == nil && orig.Cmp(dst.ToBig()) != 0 {
686703
t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst)
687704
}
688705
}
@@ -692,3 +709,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
692709
func intPtr(i int64) *int64 {
693710
return &i
694711
}
712+
713+
func BenchmarkEffectiveGasTipCmp(b *testing.B) {
714+
signer := LatestSigner(params.TestChainConfig)
715+
key, _ := crypto.GenerateKey()
716+
txdata := &DynamicFeeTx{
717+
ChainID: big.NewInt(1),
718+
Nonce: 0,
719+
GasTipCap: big.NewInt(2000000000),
720+
GasFeeCap: big.NewInt(3000000000),
721+
Gas: 21000,
722+
To: &common.Address{},
723+
Value: big.NewInt(0),
724+
Data: nil,
725+
}
726+
tx, _ := SignNewTx(key, signer, txdata)
727+
other, _ := SignNewTx(key, signer, txdata)
728+
baseFee := uint256.NewInt(1000000000) // 1 gwei
729+
730+
b.Run("Original", func(b *testing.B) {
731+
b.ReportAllocs()
732+
for i := 0; i < b.N; i++ {
733+
tx.EffectiveGasTipCmp(other, baseFee)
734+
}
735+
})
736+
}

0 commit comments

Comments
 (0)