Skip to content

Commit 22b3268

Browse files
MariusVanDerWijdenholiman
authored andcommitted
core: types: less allocations when hashing and tx handling (ethereum#21265)
* core, crypto: various allocation savings regarding tx handling * core: reduce allocs for gas price comparison This change reduces the allocations needed for comparing different transactions to each other. A call to `tx.GasPrice()` copies the gas price as it has to be safe against modifications and also needs to be threadsafe. For comparing and ordering different transactions we don't need these guarantees * core: added tx.GasPriceIntCmp for comparison without allocation adds a method to remove unneeded allocation in comparison to tx.gasPrice * core/types: pool legacykeccak256 objects in rlpHash rlpHash is by far the most used function in core that allocates a legacyKeccak256 object on each call. Since it is so widely used it makes sense to add pooling here so we relieve the GC. On my machine these changes result in > 100 MILLION less allocations and > 30 GB less allocated memory. * reverted some changes * reverted some changes * trie: use crypto.KeccakState instead of replicating code Co-authored-by: Martin Holst Swende <[email protected]>
1 parent 6c626fa commit 22b3268

File tree

9 files changed

+72
-32
lines changed

9 files changed

+72
-32
lines changed

core/tx_list.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
256256
// Have to ensure that the new gas price is higher than the old gas
257257
// price as well as checking the percentage threshold to ensure that
258258
// this is accurate for low (Wei-level) gas price replacements
259-
if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
259+
if old.GasPriceCmp(tx) >= 0 || tx.GasPriceIntCmp(threshold) < 0 {
260260
return false, nil
261261
}
262262
}
@@ -372,7 +372,7 @@ func (h priceHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
372372

373373
func (h priceHeap) Less(i, j int) bool {
374374
// Sort primarily by price, returning the cheaper one
375-
switch h[i].GasPrice().Cmp(h[j].GasPrice()) {
375+
switch h[i].GasPriceCmp(h[j]) {
376376
case -1:
377377
return true
378378
case 1:
@@ -449,7 +449,7 @@ func (l *txPricedList) Cap(threshold *big.Int, local *accountSet) types.Transact
449449
continue
450450
}
451451
// Stop the discards if we've reached the threshold
452-
if tx.GasPrice().Cmp(threshold) >= 0 {
452+
if tx.GasPriceIntCmp(threshold) >= 0 {
453453
save = append(save, tx)
454454
break
455455
}
@@ -489,7 +489,7 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo
489489
return false
490490
}
491491
cheapest := []*types.Transaction(*l.items)[0]
492-
return cheapest.GasPrice().Cmp(tx.GasPrice()) >= 0
492+
return cheapest.GasPriceCmp(tx) >= 0
493493
}
494494

495495
// Discard finds a number of most underpriced transactions, removes them from the

core/tx_list_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package core
1818

1919
import (
20+
"math/big"
2021
"math/rand"
2122
"testing"
2223

@@ -49,3 +50,21 @@ func TestStrictTxListAdd(t *testing.T) {
4950
}
5051
}
5152
}
53+
54+
func BenchmarkTxListAdd(t *testing.B) {
55+
// Generate a list of transactions to insert
56+
key, _ := crypto.GenerateKey()
57+
58+
txs := make(types.Transactions, 100000)
59+
for i := 0; i < len(txs); i++ {
60+
txs[i] = transaction(uint64(i), 0, key)
61+
}
62+
// Insert the transactions in a random order
63+
list := newTxList(true)
64+
priceLimit := big.NewInt(int64(DefaultTxPoolConfig.PriceLimit))
65+
t.ResetTimer()
66+
for _, v := range rand.Perm(len(txs)) {
67+
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
68+
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
69+
}
70+
}

core/tx_pool.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
534534
}
535535
// Drop non-local transactions under our own minimal accepted gas price
536536
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
537-
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
537+
if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 {
538538
return ErrUnderpriced
539539
}
540540
// Ensure the transaction adheres to nonce ordering
@@ -1187,26 +1187,26 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
11871187
for _, tx := range forwards {
11881188
hash := tx.Hash()
11891189
pool.all.Remove(hash)
1190-
log.Trace("Removed old queued transaction", "hash", hash)
11911190
}
1191+
log.Trace("Removed old queued transactions", "count", len(forwards))
11921192
// Drop all transactions that are too costly (low balance or out of gas)
11931193
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
11941194
for _, tx := range drops {
11951195
hash := tx.Hash()
11961196
pool.all.Remove(hash)
1197-
log.Trace("Removed unpayable queued transaction", "hash", hash)
11981197
}
1198+
log.Trace("Removed unpayable queued transactions", "count", len(drops))
11991199
queuedNofundsMeter.Mark(int64(len(drops)))
12001200

12011201
// Gather all executable transactions and promote them
12021202
readies := list.Ready(pool.pendingNonces.get(addr))
12031203
for _, tx := range readies {
12041204
hash := tx.Hash()
12051205
if pool.promoteTx(addr, hash, tx) {
1206-
log.Trace("Promoting queued transaction", "hash", hash)
12071206
promoted = append(promoted, tx)
12081207
}
12091208
}
1209+
log.Trace("Promoted queued transactions", "count", len(promoted))
12101210
queuedGauge.Dec(int64(len(readies)))
12111211

12121212
// Drop all transactions over the allowed limit

core/types/block.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,13 @@ import (
2323
"io"
2424
"math/big"
2525
"reflect"
26+
"sync"
2627
"sync/atomic"
2728
"time"
2829

2930
"github.com/ethereum/go-ethereum/common"
3031
"github.com/ethereum/go-ethereum/common/hexutil"
32+
"github.com/ethereum/go-ethereum/crypto"
3133
"github.com/ethereum/go-ethereum/rlp"
3234
"golang.org/x/crypto/sha3"
3335
)
@@ -129,10 +131,19 @@ func (h *Header) SanityCheck() error {
129131
return nil
130132
}
131133

134+
// hasherPool holds LegacyKeccak hashers.
135+
var hasherPool = sync.Pool{
136+
New: func() interface{} {
137+
return sha3.NewLegacyKeccak256()
138+
},
139+
}
140+
132141
func rlpHash(x interface{}) (h common.Hash) {
133-
hw := sha3.NewLegacyKeccak256()
134-
rlp.Encode(hw, x)
135-
hw.Sum(h[:0])
142+
sha := hasherPool.Get().(crypto.KeccakState)
143+
defer hasherPool.Put(sha)
144+
sha.Reset()
145+
rlp.Encode(sha, x)
146+
sha.Read(h[:])
136147
return h
137148
}
138149

core/types/transaction.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,15 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error {
175175
func (tx *Transaction) Data() []byte { return common.CopyBytes(tx.data.Payload) }
176176
func (tx *Transaction) Gas() uint64 { return tx.data.GasLimit }
177177
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.data.Price) }
178-
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
179-
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
180-
func (tx *Transaction) CheckNonce() bool { return true }
178+
func (tx *Transaction) GasPriceCmp(other *Transaction) int {
179+
return tx.data.Price.Cmp(other.data.Price)
180+
}
181+
func (tx *Transaction) GasPriceIntCmp(other *big.Int) int {
182+
return tx.data.Price.Cmp(other)
183+
}
184+
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.data.Amount) }
185+
func (tx *Transaction) Nonce() uint64 { return tx.data.AccountNonce }
186+
func (tx *Transaction) CheckNonce() bool { return true }
181187

182188
// To returns the recipient address of the transaction.
183189
// It returns nil if the transaction is a contract creation.

crypto/crypto.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"encoding/hex"
2525
"errors"
2626
"fmt"
27+
"hash"
2728
"io"
2829
"io/ioutil"
2930
"math/big"
@@ -51,23 +52,33 @@ var (
5152

5253
var errInvalidPubkey = errors.New("invalid secp256k1 public key")
5354

55+
// KeccakState wraps sha3.state. In addition to the usual hash methods, it also supports
56+
// Read to get a variable amount of data from the hash state. Read is faster than Sum
57+
// because it doesn't copy the internal state, but also modifies the internal state.
58+
type KeccakState interface {
59+
hash.Hash
60+
Read([]byte) (int, error)
61+
}
62+
5463
// Keccak256 calculates and returns the Keccak256 hash of the input data.
5564
func Keccak256(data ...[]byte) []byte {
56-
d := sha3.NewLegacyKeccak256()
65+
b := make([]byte, 32)
66+
d := sha3.NewLegacyKeccak256().(KeccakState)
5767
for _, b := range data {
5868
d.Write(b)
5969
}
60-
return d.Sum(nil)
70+
d.Read(b)
71+
return b
6172
}
6273

6374
// Keccak256Hash calculates and returns the Keccak256 hash of the input data,
6475
// converting it to an internal Hash data structure.
6576
func Keccak256Hash(data ...[]byte) (h common.Hash) {
66-
d := sha3.NewLegacyKeccak256()
77+
d := sha3.NewLegacyKeccak256().(KeccakState)
6778
for _, b := range data {
6879
d.Write(b)
6980
}
70-
d.Sum(h[:0])
81+
d.Read(h[:])
7182
return h
7283
}
7384

eth/gasprice/gasprice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ type transactionsByGasPrice []*types.Transaction
156156

157157
func (t transactionsByGasPrice) Len() int { return len(t) }
158158
func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
159-
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 }
159+
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 }
160160

161161
// getBlockPrices calculates the lowest transaction gas price in a given block
162162
// and sends it to the result channel. If the block is empty, price is nil.

trie/committer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"sync"
2323

2424
"github.com/ethereum/go-ethereum/common"
25+
"github.com/ethereum/go-ethereum/crypto"
2526
"github.com/ethereum/go-ethereum/rlp"
2627
"golang.org/x/crypto/sha3"
2728
)
@@ -46,7 +47,7 @@ type leaf struct {
4647
// processed sequentially - onleaf will never be called in parallel or out of order.
4748
type committer struct {
4849
tmp sliceBuffer
49-
sha keccakState
50+
sha crypto.KeccakState
5051

5152
onleaf LeafCallback
5253
leafCh chan *leaf
@@ -57,7 +58,7 @@ var committerPool = sync.Pool{
5758
New: func() interface{} {
5859
return &committer{
5960
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
60-
sha: sha3.NewLegacyKeccak256().(keccakState),
61+
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
6162
}
6263
},
6364
}

trie/hasher.go

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,13 @@
1717
package trie
1818

1919
import (
20-
"hash"
2120
"sync"
2221

22+
"github.com/ethereum/go-ethereum/crypto"
2323
"github.com/ethereum/go-ethereum/rlp"
2424
"golang.org/x/crypto/sha3"
2525
)
2626

27-
// keccakState wraps sha3.state. In addition to the usual hash methods, it also supports
28-
// Read to get a variable amount of data from the hash state. Read is faster than Sum
29-
// because it doesn't copy the internal state, but also modifies the internal state.
30-
type keccakState interface {
31-
hash.Hash
32-
Read([]byte) (int, error)
33-
}
34-
3527
type sliceBuffer []byte
3628

3729
func (b *sliceBuffer) Write(data []byte) (n int, err error) {
@@ -46,7 +38,7 @@ func (b *sliceBuffer) Reset() {
4638
// hasher is a type used for the trie Hash operation. A hasher has some
4739
// internal preallocated temp space
4840
type hasher struct {
49-
sha keccakState
41+
sha crypto.KeccakState
5042
tmp sliceBuffer
5143
parallel bool // Whether to use paralallel threads when hashing
5244
}
@@ -56,7 +48,7 @@ var hasherPool = sync.Pool{
5648
New: func() interface{} {
5749
return &hasher{
5850
tmp: make(sliceBuffer, 0, 550), // cap is as large as a full fullNode.
59-
sha: sha3.NewLegacyKeccak256().(keccakState),
51+
sha: sha3.NewLegacyKeccak256().(crypto.KeccakState),
6052
}
6153
},
6254
}

0 commit comments

Comments
 (0)