mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 03:10:48 +00:00
core, core/vm/runtime: fix hashes benchmark, add block import benchmark
This commit is contained in:
parent
ed658c8f3c
commit
fd9be88ed8
6 changed files with 156 additions and 4 deletions
|
|
@ -18,7 +18,12 @@ package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -68,6 +73,12 @@ func BenchmarkInsertChain_ring1000_memdb(b *testing.B) {
|
||||||
func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) {
|
func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) {
|
||||||
benchInsertChain(b, true, genTxRing(1000))
|
benchInsertChain(b, true, genTxRing(1000))
|
||||||
}
|
}
|
||||||
|
func BenchmarkInsertChain_evmWorkload_memdb(b *testing.B) {
|
||||||
|
benchInsertChainEVM(b, false)
|
||||||
|
}
|
||||||
|
func BenchmarkInsertChain_evmWorkload_diskdb(b *testing.B) {
|
||||||
|
benchInsertChainEVM(b, true)
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// This is the content of the genesis block used by the benchmarks.
|
// This is the content of the genesis block used by the benchmarks.
|
||||||
|
|
@ -208,6 +219,110 @@ func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The evmWorkload addresses hold the evm-bench contracts (see
|
||||||
|
// core/vm/runtime/testdata), installed through the genesis alloc. Their
|
||||||
|
// runtime bytecode sets up all state inside Benchmark(), so no constructor
|
||||||
|
// runs.
|
||||||
|
var benchWorkloadAddrs = []common.Address{
|
||||||
|
common.HexToAddress("0x00000000000000000000000000000000000e0001"), // ERC20Transfer
|
||||||
|
common.HexToAddress("0x00000000000000000000000000000000000e0002"), // ERC20Mint
|
||||||
|
common.HexToAddress("0x00000000000000000000000000000000000e0003"), // ERC20ApprovalTransfer
|
||||||
|
common.HexToAddress("0x00000000000000000000000000000000000e0004"), // TenThousandHashes
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchWorkloadCode(tb testing.TB) [][]byte {
|
||||||
|
files := []string{"erc20transfer.hex", "erc20mint.hex", "erc20approval.hex", "tenthousandhashes.hex"}
|
||||||
|
codes := make([][]byte, len(files))
|
||||||
|
for i, name := range files {
|
||||||
|
raw, err := os.ReadFile(filepath.Join("vm", "runtime", "testdata", name))
|
||||||
|
if err != nil {
|
||||||
|
tb.Fatal(err)
|
||||||
|
}
|
||||||
|
code, err := hex.DecodeString(strings.TrimSpace(string(raw)))
|
||||||
|
if err != nil {
|
||||||
|
tb.Fatal(err)
|
||||||
|
}
|
||||||
|
codes[i] = code
|
||||||
|
}
|
||||||
|
return codes
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchInsertChainEVM measures InsertChain over blocks dominated by real
|
||||||
|
// contract execution, as a coarse local surrogate for sync throughput: the
|
||||||
|
// timed path is full block processing, including sender recovery, EVM
|
||||||
|
// execution, state updates, receipts, bloom filters and the state root.
|
||||||
|
// Every block invokes the four evm-bench workloads (ERC-20 transfer, mint
|
||||||
|
// and approval loops plus a keccak loop) and sends ten value transfers to
|
||||||
|
// fresh accounts so the state grows as it would during sync. Reports Mgas/s
|
||||||
|
// alongside the standard timings.
|
||||||
|
func benchInsertChainEVM(b *testing.B, disk bool) {
|
||||||
|
var db ethdb.Database
|
||||||
|
if !disk {
|
||||||
|
db = rawdb.NewMemoryDatabase()
|
||||||
|
} else {
|
||||||
|
pdb, err := pebble.New(b.TempDir(), 128, 128, "", false)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("cannot create temporary database: %v", err)
|
||||||
|
}
|
||||||
|
db = rawdb.NewDatabase(pdb)
|
||||||
|
defer db.Close()
|
||||||
|
}
|
||||||
|
alloc := types.GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}
|
||||||
|
for i, code := range benchWorkloadCode(b) {
|
||||||
|
alloc[benchWorkloadAddrs[i]] = types.Account{Code: code, Balance: new(big.Int)}
|
||||||
|
}
|
||||||
|
gspec := &Genesis{
|
||||||
|
Config: params.TestChainConfig,
|
||||||
|
Alloc: alloc,
|
||||||
|
GasLimit: 150_000_000,
|
||||||
|
}
|
||||||
|
selector := []byte{0x30, 0x62, 0x7b, 0x7c} // Benchmark()
|
||||||
|
_, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), b.N, func(i int, gen *BlockGen) {
|
||||||
|
signer := gen.Signer()
|
||||||
|
gasPrice := big.NewInt(0)
|
||||||
|
if gen.header.BaseFee != nil {
|
||||||
|
gasPrice = gen.header.BaseFee
|
||||||
|
}
|
||||||
|
for _, addr := range benchWorkloadAddrs {
|
||||||
|
to := addr
|
||||||
|
tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{
|
||||||
|
Nonce: gen.TxNonce(benchRootAddr),
|
||||||
|
To: &to,
|
||||||
|
Gas: 30_000_000,
|
||||||
|
Data: selector,
|
||||||
|
GasPrice: gasPrice,
|
||||||
|
})
|
||||||
|
gen.AddTx(tx)
|
||||||
|
}
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
var to common.Address
|
||||||
|
binary.BigEndian.PutUint64(to[4:12], uint64(i+1))
|
||||||
|
binary.BigEndian.PutUint64(to[12:20], uint64(j+1))
|
||||||
|
tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{
|
||||||
|
Nonce: gen.TxNonce(benchRootAddr),
|
||||||
|
To: &to,
|
||||||
|
Value: big.NewInt(1),
|
||||||
|
Gas: params.TxGas,
|
||||||
|
GasPrice: gasPrice,
|
||||||
|
})
|
||||||
|
gen.AddTx(tx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var totalGas uint64
|
||||||
|
for _, block := range chain {
|
||||||
|
totalGas += block.GasUsed()
|
||||||
|
}
|
||||||
|
chainman, _ := NewBlockChain(db, gspec, ethash.NewFaker(), nil)
|
||||||
|
defer chainman.Stop()
|
||||||
|
b.ReportAllocs()
|
||||||
|
b.ResetTimer()
|
||||||
|
if i, err := chainman.InsertChain(chain); err != nil {
|
||||||
|
b.Fatalf("insert error (block %d): %v\n", i, err)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
b.ReportMetric(float64(totalGas)/1e6/b.Elapsed().Seconds(), "Mgas/s")
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkChainRead_header_10k(b *testing.B) {
|
func BenchmarkChainRead_header_10k(b *testing.B) {
|
||||||
benchReadChain(b, false, 10000)
|
benchReadChain(b, false, 10000)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
core/vm/runtime/testdata/evm-bench/README.md
vendored
15
core/vm/runtime/testdata/evm-bench/README.md
vendored
|
|
@ -17,7 +17,7 @@ workload:
|
||||||
| `ERC20Transfer` | ERC-20 transfers in a loop |
|
| `ERC20Transfer` | ERC-20 transfers in a loop |
|
||||||
| `ERC20Mint` | 5000 `_mint`s (SSTORE/keccak/LOG-heavy) |
|
| `ERC20Mint` | 5000 `_mint`s (SSTORE/keccak/LOG-heavy) |
|
||||||
| `ERC20ApprovalTransfer` | 1000 approve+transferFrom cycles |
|
| `ERC20ApprovalTransfer` | 1000 approve+transferFrom cycles |
|
||||||
| `TenThousandHashes` | a keccak loop |
|
| `TenThousandHashes` | 20000 chained keccak256 calls |
|
||||||
|
|
||||||
## Running
|
## Running
|
||||||
|
|
||||||
|
|
@ -40,6 +40,16 @@ interpreter change is committed) and runs `benchstat`.
|
||||||
the evm-bench Solidity sources (solc 0.8.17 via docker). These contracts set up
|
the evm-bench Solidity sources (solc 0.8.17 via docker). These contracts set up
|
||||||
their state inside `Benchmark()`, so the runtime bytecode is callable directly.
|
their state inside `Benchmark()`, so the runtime bytecode is callable directly.
|
||||||
|
|
||||||
|
`TenThousandHashes` is compiled from the vendored
|
||||||
|
[`TenThousandHashes.sol`](TenThousandHashes.sol) in this directory, not the
|
||||||
|
upstream evm-bench file. Upstream discards the hash result, so solc's optimizer
|
||||||
|
deletes the keccak256 and the compiled benchmark degenerates into a bare
|
||||||
|
counter loop performing a single static hash. The vendored copy chains the hash
|
||||||
|
through a returned accumulator so the optimized bytecode performs all 20000
|
||||||
|
hashes. Numbers for this benchmark are therefore not comparable with evm-bench
|
||||||
|
or gevm published tables, which use degenerate bytecode (gevm's local
|
||||||
|
replacement is broken differently, an inverted loop bound that hashes once).
|
||||||
|
|
||||||
`snailtracer.hex` is **vendored, not regenerated by gen.sh**: evm-bench's
|
`snailtracer.hex` is **vendored, not regenerated by gen.sh**: evm-bench's
|
||||||
`SnailTracer` initializes its scene in the constructor, so its runtime bytecode
|
`SnailTracer` initializes its scene in the constructor, so its runtime bytecode
|
||||||
(no constructor) renders an empty scene. The committed file is a runtime-callable
|
(no constructor) renders an empty scene. The committed file is a runtime-callable
|
||||||
|
|
@ -53,4 +63,5 @@ The `.hex` files are committed, so running the benchmarks needs no toolchain.
|
||||||
- The contract constructor is not executed, so storage starts empty. The
|
- The contract constructor is not executed, so storage starts empty. The
|
||||||
evm-bench contracts set up whatever state they need inside `Benchmark()`,
|
evm-bench contracts set up whatever state they need inside `Benchmark()`,
|
||||||
matching the work the evm-bench harness measures.
|
matching the work the evm-bench harness measures.
|
||||||
- Names match evm-bench / gevm for cross-comparison.
|
- Names match evm-bench / gevm for cross-comparison, except `TenThousandHashes`
|
||||||
|
(see the regenerating section).
|
||||||
|
|
|
||||||
17
core/vm/runtime/testdata/evm-bench/TenThousandHashes.sol
vendored
Normal file
17
core/vm/runtime/testdata/evm-bench/TenThousandHashes.sol
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
pragma solidity ^0.8.17;
|
||||||
|
|
||||||
|
// Vendored from evm-bench (github.com/ziyadedher/evm-bench) with one fix.
|
||||||
|
// The upstream loop discards the keccak256 result, so solc's optimizer
|
||||||
|
// removes the pure call entirely and the compiled benchmark degenerates
|
||||||
|
// into a bare counter loop that performs a single static hash. Chaining
|
||||||
|
// the hash through an accumulator that the function returns keeps all
|
||||||
|
// 20000 hashes in the optimized bytecode. The Benchmark() selector is
|
||||||
|
// unchanged because return types are not part of the signature.
|
||||||
|
contract TenThousandHashes {
|
||||||
|
function Benchmark() external pure returns (bytes32 acc) {
|
||||||
|
for (uint256 i = 0; i < 20000; i++) {
|
||||||
|
acc = keccak256(abi.encodePacked(acc, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -28,7 +28,7 @@ COUNT="${2:-10}"
|
||||||
# pattern, so the loops run as their own invocation anyway).
|
# pattern, so the loops run as their own invocation anyway).
|
||||||
BENCHES=(
|
BENCHES=(
|
||||||
'^BenchmarkSnailtracer$:20x'
|
'^BenchmarkSnailtracer$:20x'
|
||||||
'^BenchmarkTenThousandHashes$:200x'
|
'^BenchmarkTenThousandHashes$:100x'
|
||||||
'^BenchmarkERC20Transfer$:100x'
|
'^BenchmarkERC20Transfer$:100x'
|
||||||
'^BenchmarkERC20Mint$:150x'
|
'^BenchmarkERC20Mint$:150x'
|
||||||
'^BenchmarkERC20ApprovalTransfer$:120x'
|
'^BenchmarkERC20ApprovalTransfer$:120x'
|
||||||
|
|
|
||||||
9
core/vm/runtime/testdata/evm-bench/gen.sh
vendored
9
core/vm/runtime/testdata/evm-bench/gen.sh
vendored
|
|
@ -8,15 +8,24 @@
|
||||||
# contract's pragma: 0.8.17 for the ERC-20/hashing contracts, 0.4.26 for the
|
# contract's pragma: 0.8.17 for the ERC-20/hashing contracts, 0.4.26 for the
|
||||||
# legacy SnailTracer.
|
# legacy SnailTracer.
|
||||||
#
|
#
|
||||||
|
# TenThousandHashes is compiled from the vendored TenThousandHashes.sol in
|
||||||
|
# this directory rather than the upstream file. Upstream discards the hash
|
||||||
|
# result, so the optimizer deletes the keccak256 and the benchmark degenerates
|
||||||
|
# into a bare counter loop. The vendored copy chains the hash through a
|
||||||
|
# returned accumulator, which keeps all 20000 hashes in the bytecode. See the
|
||||||
|
# comment in that file.
|
||||||
|
#
|
||||||
# Usage: core/vm/runtime/testdata/evm-bench/gen.sh
|
# Usage: core/vm/runtime/testdata/evm-bench/gen.sh
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
TD="$(cd "$(dirname "$0")/.." && pwd)" # .../core/vm/runtime/testdata
|
TD="$(cd "$(dirname "$0")/.." && pwd)" # .../core/vm/runtime/testdata
|
||||||
|
HERE="$(cd "$(dirname "$0")" && pwd)" # .../testdata/evm-bench
|
||||||
WORK="$(mktemp -d)"
|
WORK="$(mktemp -d)"
|
||||||
trap 'rm -rf "$WORK"' EXIT
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
|
||||||
git clone --depth 1 https://github.com/ziyadedher/evm-bench "$WORK/evm-bench" >/dev/null 2>&1
|
git clone --depth 1 https://github.com/ziyadedher/evm-bench "$WORK/evm-bench" >/dev/null 2>&1
|
||||||
B="$WORK/evm-bench/benchmarks"
|
B="$WORK/evm-bench/benchmarks"
|
||||||
|
cp "$HERE/TenThousandHashes.sol" "$B/ten-thousand-hashes/TenThousandHashes.sol"
|
||||||
|
|
||||||
# ERC-20 (transfer/mint/approval) + ten-thousand-hashes: pragma ^0.8.17. These
|
# ERC-20 (transfer/mint/approval) + ten-thousand-hashes: pragma ^0.8.17. These
|
||||||
# set up all their state inside Benchmark(), so the runtime bytecode is callable
|
# set up all their state inside Benchmark(), so the runtime bytecode is callable
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
6080604052348015600f57600080fd5b506004361060285760003560e01c806330627b7c14602d575b600080fd5b60336035565b005b60005b614e20811015606a5760408051602081018390520160408051601f198184030190525280606381606d565b9150506038565b50565b600060018201608c57634e487b7160e01b600052601160045260246000fd5b506001019056fea2646970667358221220e499ea6d1ccd5fce28e8cd2507f6fef33f0d005543e04580f290bbd01df0655464736f6c63430008110033
|
6080604052348015600f57600080fd5b506004361060285760003560e01c806330627b7c14602d575b600080fd5b60336045565b60405190815260200160405180910390f35b6000805b614e20811015608e57604080516020810184905290810182905260600160405160208183030381529060405280519060200120915080806087906092565b9150506049565b5090565b60006001820160b157634e487b7160e01b600052601160045260246000fd5b506001019056fea26469706673582212207f65e4c71aef311b81ff8b065cdd78f5be33f5dc80d3b2318e118075e33697f164736f6c63430008110033
|
||||||
Loading…
Reference in a new issue