mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-15 20:46:40 +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()
|
pool.mu.Lock()
|
||||||
defer pool.mu.Unlock()
|
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))
|
pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
|
||||||
for addr, list := range pool.pending {
|
for addr, list := range pool.pending {
|
||||||
txs := list.Flatten()
|
txs := list.Flatten()
|
||||||
|
|
||||||
// If the miner requests tip enforcement, cap the lists now
|
// 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 {
|
for i, tx := range txs {
|
||||||
if minTipBig != nil {
|
if filter.MinTip != nil {
|
||||||
if tx.EffectiveGasTipIntCmp(minTipBig, baseFeeBig) < 0 {
|
if tx.EffectiveGasTipIntCmp(filter.MinTip, filter.BaseFee) < 0 {
|
||||||
txs = txs[:i]
|
txs = txs[:i]
|
||||||
break
|
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.
|
// 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.
|
// If baseFee is nil then the sorting is based on gasFeeCap.
|
||||||
type priceHeap struct {
|
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
|
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
|
// 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.
|
// necessary to call right before SetBaseFee when processing a new block.
|
||||||
func (l *pricedList) SetBaseFee(baseFee *big.Int) {
|
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()
|
l.Reheap()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -36,6 +37,7 @@ var (
|
||||||
ErrInvalidTxType = errors.New("transaction type not valid in this context")
|
ErrInvalidTxType = errors.New("transaction type not valid in this context")
|
||||||
ErrTxTypeNotSupported = errors.New("transaction type not supported")
|
ErrTxTypeNotSupported = errors.New("transaction type not supported")
|
||||||
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
|
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")
|
errShortTypedTx = errors.New("typed transaction too short")
|
||||||
errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
|
errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
|
||||||
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
|
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.
|
// EffectiveGasTip returns the effective miner gasTipCap for the given base fee.
|
||||||
// Note: if the effective gasTipCap is negative, this method returns both error
|
// Note: if the effective gasTipCap would be negative, this method
|
||||||
// the actual negative value, _and_ ErrGasFeeCapTooLow
|
// returns ErrGasFeeCapTooLow, and value is undefined.
|
||||||
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
||||||
dst := new(big.Int)
|
dst := new(uint256.Int)
|
||||||
err := tx.calcEffectiveGasTip(dst, baseFee)
|
base := new(uint256.Int)
|
||||||
return dst, err
|
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
|
// calcEffectiveGasTip calculates the effective gas tip of the transaction and
|
||||||
// saves the result to dst.
|
// 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 {
|
if baseFee == nil {
|
||||||
dst.Set(tx.inner.gasTipCap())
|
if dst.SetFromBig(tx.inner.gasTipCap()) {
|
||||||
|
return ErrUint256Overflow
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
gasFeeCap := tx.inner.gasFeeCap()
|
if dst.SetFromBig(tx.inner.gasFeeCap()) {
|
||||||
if gasFeeCap.Cmp(baseFee) < 0 {
|
return ErrUint256Overflow
|
||||||
|
}
|
||||||
|
if dst.Cmp(baseFee) < 0 {
|
||||||
err = ErrGasFeeCapTooLow
|
err = ErrGasFeeCapTooLow
|
||||||
}
|
}
|
||||||
|
|
||||||
dst.Sub(gasFeeCap, baseFee)
|
dst.Sub(dst, baseFee)
|
||||||
gasTipCap := tx.inner.gasTipCap()
|
gasTipCap := new(uint256.Int)
|
||||||
|
if gasTipCap.SetFromBig(tx.inner.gasTipCap()) {
|
||||||
|
return ErrUint256Overflow
|
||||||
|
}
|
||||||
if gasTipCap.Cmp(dst) < 0 {
|
if gasTipCap.Cmp(dst) < 0 {
|
||||||
dst.Set(gasTipCap)
|
dst.Set(gasTipCap)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
|
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *uint256.Int) int {
|
||||||
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
|
|
||||||
if baseFee == nil {
|
if baseFee == nil {
|
||||||
return tx.GasTipCapCmp(other)
|
return tx.GasTipCapCmp(other)
|
||||||
}
|
}
|
||||||
// Use more efficient internal method.
|
// 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)
|
tx.calcEffectiveGasTip(txTip, baseFee)
|
||||||
other.calcEffectiveGasTip(otherTip, baseFee)
|
other.calcEffectiveGasTip(otherTip, baseFee)
|
||||||
return txTip.Cmp(otherTip)
|
return txTip.Cmp(otherTip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
|
// 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 {
|
if baseFee == nil {
|
||||||
return tx.GasTipCapIntCmp(other)
|
return tx.GasTipCapIntCmp(other.ToBig())
|
||||||
}
|
}
|
||||||
txTip := new(big.Int)
|
txTip := new(uint256.Int)
|
||||||
tx.calcEffectiveGasTip(txTip, baseFee)
|
tx.calcEffectiveGasTip(txTip, baseFee)
|
||||||
return txTip.Cmp(other)
|
return txTip.Cmp(other)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The values in those tests are from the Transaction Tests
|
// The values in those tests are from the Transaction Tests
|
||||||
|
|
@ -609,12 +610,12 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
|
||||||
Data: nil,
|
Data: nil,
|
||||||
}
|
}
|
||||||
tx, _ := SignNewTx(key, signer, txdata)
|
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.Run("Original", func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := tx.EffectiveGasTip(baseFee)
|
_, err := tx.EffectiveGasTip(baseFee.ToBig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -623,7 +624,7 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
|
||||||
|
|
||||||
b.Run("IntoMethod", func(b *testing.B) {
|
b.Run("IntoMethod", func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
dst := new(big.Int)
|
dst := new(uint256.Int)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
err := tx.calcEffectiveGasTip(dst, baseFee)
|
err := tx.calcEffectiveGasTip(dst, baseFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -634,9 +635,6 @@ func BenchmarkEffectiveGasTip(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEffectiveGasTipInto(t *testing.T) {
|
func TestEffectiveGasTipInto(t *testing.T) {
|
||||||
signer := LatestSigner(params.TestChainConfig)
|
|
||||||
key, _ := crypto.GenerateKey()
|
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
tipCap int64
|
tipCap int64
|
||||||
feeCap int64
|
feeCap int64
|
||||||
|
|
@ -652,8 +650,26 @@ func TestEffectiveGasTipInto(t *testing.T) {
|
||||||
{tipCap: 50, feeCap: 100, baseFee: nil}, // nil base fee
|
{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 {
|
for i, tc := range testCases {
|
||||||
txdata := &DynamicFeeTx{
|
tx := NewTx(&DynamicFeeTx{
|
||||||
ChainID: big.NewInt(1),
|
ChainID: big.NewInt(1),
|
||||||
Nonce: 0,
|
Nonce: 0,
|
||||||
GasTipCap: big.NewInt(tc.tipCap),
|
GasTipCap: big.NewInt(tc.tipCap),
|
||||||
|
|
@ -662,27 +678,28 @@ func TestEffectiveGasTipInto(t *testing.T) {
|
||||||
To: &common.Address{},
|
To: &common.Address{},
|
||||||
Value: big.NewInt(0),
|
Value: big.NewInt(0),
|
||||||
Data: nil,
|
Data: nil,
|
||||||
}
|
})
|
||||||
tx, _ := SignNewTx(key, signer, txdata)
|
|
||||||
|
|
||||||
var baseFee *big.Int
|
var baseFee *big.Int
|
||||||
|
var baseFee2 *uint256.Int
|
||||||
if tc.baseFee != nil {
|
if tc.baseFee != nil {
|
||||||
baseFee = big.NewInt(*tc.baseFee)
|
baseFee = big.NewInt(*tc.baseFee)
|
||||||
|
baseFee2 = uint256.NewInt(uint64(*tc.baseFee))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get result from original method
|
// Get result from original method
|
||||||
orig, origErr := tx.EffectiveGasTip(baseFee)
|
orig, origErr := orig(tx, baseFee)
|
||||||
|
|
||||||
// Get result from new method
|
// Get result from new method
|
||||||
dst := new(big.Int)
|
dst := new(uint256.Int)
|
||||||
newErr := tx.calcEffectiveGasTip(dst, baseFee)
|
newErr := tx.calcEffectiveGasTip(dst, baseFee2)
|
||||||
|
|
||||||
// Compare results
|
// Compare results
|
||||||
if (origErr != nil) != (newErr != nil) {
|
if (origErr != nil) != (newErr != nil) {
|
||||||
t.Fatalf("case %d: error mismatch: orig %v, new %v", i, origErr, newErr)
|
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)
|
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 {
|
func intPtr(i int64) *int64 {
|
||||||
return &i
|
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