mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 15:47:21 +00:00
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>
This commit is contained in:
parent
f3467d1e63
commit
10421edf3e
4 changed files with 95 additions and 46 deletions
|
|
@ -514,26 +514,15 @@ func (pool *LegacyPool) Pending(filter txpool.PendingFilter) map[common.Address]
|
|||
pool.mu.Lock()
|
||||
defer pool.mu.Unlock()
|
||||
|
||||
// Convert the new uint256.Int types to the old big.Int ones used by the legacy pool
|
||||
var (
|
||||
minTipBig *big.Int
|
||||
baseFeeBig *big.Int
|
||||
)
|
||||
if filter.MinTip != nil {
|
||||
minTipBig = filter.MinTip.ToBig()
|
||||
}
|
||||
if filter.BaseFee != nil {
|
||||
baseFeeBig = filter.BaseFee.ToBig()
|
||||
}
|
||||
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
||||
for addr, list := range pool.pending {
|
||||
txs := list.Flatten()
|
||||
|
||||
// If the miner requests tip enforcement, cap the lists now
|
||||
if minTipBig != nil || filter.GasLimitCap != 0 {
|
||||
if filter.MinTip != nil || filter.GasLimitCap != 0 {
|
||||
for i, tx := range txs {
|
||||
if minTipBig != nil {
|
||||
if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 {
|
||||
if filter.MinTip != nil {
|
||||
if tx.EffectiveGasTipIntCmp(filter.MinTip, filter.BaseFee) < 0 {
|
||||
txs = txs[:i]
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -475,7 +475,7 @@ func (l *list) subTotalCost(txs []*types.Transaction) {
|
|||
// then the heap is sorted based on the effective tip based on the given base fee.
|
||||
// If baseFee is nil then the sorting is based on gasFeeCap.
|
||||
type priceHeap struct {
|
||||
baseFee *big.Int // heap should always be re-sorted after baseFee is changed
|
||||
baseFee *uint256.Int // heap should always be re-sorted after baseFee is changed
|
||||
list []*types.Transaction
|
||||
}
|
||||
|
||||
|
|
@ -677,6 +677,10 @@ func (l *pricedList) Reheap() {
|
|||
// SetBaseFee updates the base fee and triggers a re-heap. Note that Removed is not
|
||||
// necessary to call right before SetBaseFee when processing a new block.
|
||||
func (l *pricedList) SetBaseFee(baseFee *big.Int) {
|
||||
l.urgent.baseFee = baseFee
|
||||
base := new(uint256.Int)
|
||||
if baseFee != nil {
|
||||
base.SetFromBig(baseFee)
|
||||
}
|
||||
l.urgent.baseFee = base
|
||||
l.Reheap()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -36,6 +37,7 @@ var (
|
|||
ErrInvalidTxType = errors.New("transaction type not valid in this context")
|
||||
ErrTxTypeNotSupported = errors.New("transaction type not supported")
|
||||
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
|
||||
ErrUint256Overflow = errors.New("bigint overflow, too large for uint256")
|
||||
errShortTypedTx = errors.New("typed transaction too short")
|
||||
errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
|
||||
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
|
||||
|
|
@ -352,54 +354,66 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
|
|||
}
|
||||
|
||||
// EffectiveGasTip returns the effective miner gasTipCap for the given base fee.
|
||||
// Note: if the effective gasTipCap is negative, this method returns both error
|
||||
// the actual negative value, _and_ ErrGasFeeCapTooLow
|
||||
// Note: if the effective gasTipCap would be negative, this method
|
||||
// returns ErrGasFeeCapTooLow, and value is undefined.
|
||||
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
||||
dst := new(big.Int)
|
||||
err := tx.calcEffectiveGasTip(dst, baseFee)
|
||||
return dst, err
|
||||
dst := new(uint256.Int)
|
||||
base := new(uint256.Int)
|
||||
if baseFee != nil {
|
||||
if base.SetFromBig(baseFee) {
|
||||
return nil, ErrUint256Overflow
|
||||
}
|
||||
}
|
||||
err := tx.calcEffectiveGasTip(dst, base)
|
||||
return dst.ToBig(), err
|
||||
}
|
||||
|
||||
// calcEffectiveGasTip calculates the effective gas tip of the transaction and
|
||||
// saves the result to dst.
|
||||
func (tx *Transaction) calcEffectiveGasTip(dst *big.Int, baseFee *big.Int) error {
|
||||
func (tx *Transaction) calcEffectiveGasTip(dst *uint256.Int, baseFee *uint256.Int) error {
|
||||
if baseFee == nil {
|
||||
dst.Set(tx.inner.gasTipCap())
|
||||
if dst.SetFromBig(tx.inner.gasTipCap()) {
|
||||
return ErrUint256Overflow
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
gasFeeCap := tx.inner.gasFeeCap()
|
||||
if gasFeeCap.Cmp(baseFee) < 0 {
|
||||
if dst.SetFromBig(tx.inner.gasFeeCap()) {
|
||||
return ErrUint256Overflow
|
||||
}
|
||||
if dst.Cmp(baseFee) < 0 {
|
||||
err = ErrGasFeeCapTooLow
|
||||
}
|
||||
|
||||
dst.Sub(gasFeeCap, baseFee)
|
||||
gasTipCap := tx.inner.gasTipCap()
|
||||
dst.Sub(dst, baseFee)
|
||||
gasTipCap := new(uint256.Int)
|
||||
if gasTipCap.SetFromBig(tx.inner.gasTipCap()) {
|
||||
return ErrUint256Overflow
|
||||
}
|
||||
if gasTipCap.Cmp(dst) < 0 {
|
||||
dst.Set(gasTipCap)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
|
||||
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
|
||||
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int {
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCapCmp(other)
|
||||
}
|
||||
// Use more efficient internal method.
|
||||
txTip, otherTip := new(big.Int), new(big.Int)
|
||||
txTip, otherTip := new(uint256.Int), new(uint256.Int)
|
||||
tx.calcEffectiveGasTip(txTip, baseFee)
|
||||
other.calcEffectiveGasTip(otherTip, baseFee)
|
||||
return txTip.Cmp(otherTip)
|
||||
}
|
||||
|
||||
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
|
||||
func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int {
|
||||
func (tx *Transaction) EffectiveGasTipIntCmp(other *uint256.Int, baseFee *uint256.Int) int {
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCapIntCmp(other)
|
||||
return tx.GasTipCapIntCmp(other.ToBig())
|
||||
}
|
||||
txTip := new(big.Int)
|
||||
txTip := new(uint256.Int)
|
||||
tx.calcEffectiveGasTip(txTip, baseFee)
|
||||
return txTip.Cmp(other)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// The values in those tests are from the Transaction Tests
|
||||
|
|
@ -609,12 +610,12 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
|
|||
Data: nil,
|
||||
}
|
||||
tx, _ := SignNewTx(key, signer, txdata)
|
||||
baseFee := big.NewInt(1000000000) // 1 gwei
|
||||
baseFee := uint256.NewInt(1000000000) // 1 gwei
|
||||
|
||||
b.Run("Original", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := tx.EffectiveGasTip(baseFee)
|
||||
_, err := tx.EffectiveGasTip(baseFee.ToBig())
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
|
@ -623,7 +624,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
|
|||
|
||||
b.Run("IntoMethod", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
dst := new(big.Int)
|
||||
dst := new(uint256.Int)
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := tx.calcEffectiveGasTip(dst, baseFee)
|
||||
if err != nil {
|
||||
|
|
@ -634,9 +635,6 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
|
|||
}
|
||||
|
||||
func TestEffectiveGasTipInto(t *testing.T) {
|
||||
signer := LatestSigner(params.TestChainConfig)
|
||||
key, _ := crypto.GenerateKey()
|
||||
|
||||
testCases := []struct {
|
||||
tipCap int64
|
||||
feeCap int64
|
||||
|
|
@ -652,8 +650,26 @@ func TestEffectiveGasTipInto(t *testing.T) {
|
|||
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
|
||||
}
|
||||
|
||||
// original, non-allocation golfed version
|
||||
orig := func(tx *Transaction, baseFee *big.Int) (*big.Int, error) {
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCap(), nil
|
||||
}
|
||||
var err error
|
||||
gasFeeCap := tx.GasFeeCap()
|
||||
if gasFeeCap.Cmp(baseFee) < 0 {
|
||||
err = ErrGasFeeCapTooLow
|
||||
}
|
||||
gasFeeCap = gasFeeCap.Sub(gasFeeCap, baseFee)
|
||||
gasTipCap := tx.GasTipCap()
|
||||
if gasTipCap.Cmp(gasFeeCap) < 0 {
|
||||
return gasTipCap, err
|
||||
}
|
||||
return gasFeeCap, err
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
txdata := &DynamicFeeTx{
|
||||
tx := NewTx(&DynamicFeeTx{
|
||||
ChainID: big.NewInt(1),
|
||||
Nonce: 0,
|
||||
GasTipCap: big.NewInt(tc.tipCap),
|
||||
|
|
@ -662,27 +678,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
|
|||
To: &common.Address{},
|
||||
Value: big.NewInt(0),
|
||||
Data: nil,
|
||||
}
|
||||
tx, _ := SignNewTx(key, signer, txdata)
|
||||
})
|
||||
|
||||
var baseFee *big.Int
|
||||
var baseFee2 *uint256.Int
|
||||
if tc.baseFee != nil {
|
||||
baseFee = big.NewInt(*tc.baseFee)
|
||||
baseFee2 = uint256.NewInt(uint64(*tc.baseFee))
|
||||
}
|
||||
|
||||
// Get result from original method
|
||||
orig, origErr := tx.EffectiveGasTip(baseFee)
|
||||
orig, origErr := orig(tx, baseFee)
|
||||
|
||||
// Get result from new method
|
||||
dst := new(big.Int)
|
||||
newErr := tx.calcEffectiveGasTip(dst, baseFee)
|
||||
dst := new(uint256.Int)
|
||||
newErr := tx.calcEffectiveGasTip(dst, baseFee2)
|
||||
|
||||
// Compare results
|
||||
if (origErr != nil) != (newErr != nil) {
|
||||
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
|
||||
}
|
||||
|
||||
if orig.Cmp(dst) != 0 {
|
||||
if origErr == nil && orig.Cmp(dst.ToBig()) != 0 {
|
||||
t.Fatalf("case %d: result mismatch: orig %v, new %v", i, orig, dst)
|
||||
}
|
||||
}
|
||||
|
|
@ -692,3 +709,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
|
|||
func intPtr(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
func BenchmarkEffectiveGasTipCmp(b *testing.B) {
|
||||
signer := LatestSigner(params.TestChainConfig)
|
||||
key, _ := crypto.GenerateKey()
|
||||
txdata := &DynamicFeeTx{
|
||||
ChainID: big.NewInt(1),
|
||||
Nonce: 0,
|
||||
GasTipCap: big.NewInt(2000000000),
|
||||
GasFeeCap: big.NewInt(3000000000),
|
||||
Gas: 21000,
|
||||
To: &common.Address{},
|
||||
Value: big.NewInt(0),
|
||||
Data: nil,
|
||||
}
|
||||
tx, _ := SignNewTx(key, signer, txdata)
|
||||
other, _ := SignNewTx(key, signer, txdata)
|
||||
baseFee := uint256.NewInt(1000000000) // 1 gwei
|
||||
|
||||
b.Run("Original", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx.EffectiveGasTipCmp(other, baseFee)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue