github.com/ethereum/go-ethereum@v1.16.1/core/bench_test.go (about) 1 // Copyright 2015 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package core 18 19 import ( 20 "crypto/ecdsa" 21 "math/big" 22 "testing" 23 24 "github.com/ethereum/go-ethereum/common" 25 "github.com/ethereum/go-ethereum/common/math" 26 "github.com/ethereum/go-ethereum/consensus/ethash" 27 "github.com/ethereum/go-ethereum/core/rawdb" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/ethereum/go-ethereum/ethdb" 31 "github.com/ethereum/go-ethereum/ethdb/pebble" 32 "github.com/ethereum/go-ethereum/params" 33 ) 34 35 func BenchmarkInsertChain_empty_memdb(b *testing.B) { 36 benchInsertChain(b, false, nil) 37 } 38 func BenchmarkInsertChain_empty_diskdb(b *testing.B) { 39 benchInsertChain(b, true, nil) 40 } 41 func BenchmarkInsertChain_valueTx_memdb(b *testing.B) { 42 benchInsertChain(b, false, genValueTx(0)) 43 } 44 func BenchmarkInsertChain_valueTx_diskdb(b *testing.B) { 45 benchInsertChain(b, true, genValueTx(0)) 46 } 47 func BenchmarkInsertChain_valueTx_100kB_memdb(b *testing.B) { 48 benchInsertChain(b, false, genValueTx(100*1024)) 49 } 50 func BenchmarkInsertChain_valueTx_100kB_diskdb(b *testing.B) { 51 benchInsertChain(b, true, genValueTx(100*1024)) 52 } 53 func BenchmarkInsertChain_uncles_memdb(b *testing.B) { 54 benchInsertChain(b, false, genUncles) 55 } 56 func BenchmarkInsertChain_uncles_diskdb(b *testing.B) { 57 benchInsertChain(b, true, genUncles) 58 } 59 func BenchmarkInsertChain_ring200_memdb(b *testing.B) { 60 benchInsertChain(b, false, genTxRing(200)) 61 } 62 func BenchmarkInsertChain_ring200_diskdb(b *testing.B) { 63 benchInsertChain(b, true, genTxRing(200)) 64 } 65 func BenchmarkInsertChain_ring1000_memdb(b *testing.B) { 66 benchInsertChain(b, false, genTxRing(1000)) 67 } 68 func BenchmarkInsertChain_ring1000_diskdb(b *testing.B) { 69 benchInsertChain(b, true, genTxRing(1000)) 70 } 71 72 var ( 73 // This is the content of the genesis block used by the benchmarks. 74 benchRootKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") 75 benchRootAddr = crypto.PubkeyToAddress(benchRootKey.PublicKey) 76 benchRootFunds = math.BigPow(2, 200) 77 ) 78 79 // genValueTx returns a block generator that includes a single 80 // value-transfer transaction with n bytes of extra data in each 81 // block. 82 func genValueTx(nbytes int) func(int, *BlockGen) { 83 // We can reuse the data for all transactions. 84 // During signing, the method tx.WithSignature(s, sig) 85 // performs: 86 // cpy := tx.inner.copy() 87 // cpy.setSignatureValues(signer.ChainID(), v, r, s) 88 // After this operation, the data can be reused by the caller. 89 data := make([]byte, nbytes) 90 return func(i int, gen *BlockGen) { 91 toaddr := common.Address{} 92 gas, _ := IntrinsicGas(data, nil, nil, false, false, false, false) 93 signer := gen.Signer() 94 gasPrice := big.NewInt(0) 95 if gen.header.BaseFee != nil { 96 gasPrice = gen.header.BaseFee 97 } 98 tx, _ := types.SignNewTx(benchRootKey, signer, &types.LegacyTx{ 99 Nonce: gen.TxNonce(benchRootAddr), 100 To: &toaddr, 101 Value: big.NewInt(1), 102 Gas: gas, 103 Data: data, 104 GasPrice: gasPrice, 105 }) 106 gen.AddTx(tx) 107 } 108 } 109 110 var ( 111 ringKeys = make([]*ecdsa.PrivateKey, 1000) 112 ringAddrs = make([]common.Address, len(ringKeys)) 113 ) 114 115 func init() { 116 ringKeys[0] = benchRootKey 117 ringAddrs[0] = benchRootAddr 118 for i := 1; i < len(ringKeys); i++ { 119 ringKeys[i], _ = crypto.GenerateKey() 120 ringAddrs[i] = crypto.PubkeyToAddress(ringKeys[i].PublicKey) 121 } 122 } 123 124 // genTxRing returns a block generator that sends ether in a ring 125 // among n accounts. This is creates n entries in the state database 126 // and fills the blocks with many small transactions. 127 func genTxRing(naccounts int) func(int, *BlockGen) { 128 from := 0 129 availableFunds := new(big.Int).Set(benchRootFunds) 130 return func(i int, gen *BlockGen) { 131 block := gen.PrevBlock(i - 1) 132 gas := block.GasLimit() 133 gasPrice := big.NewInt(0) 134 if gen.header.BaseFee != nil { 135 gasPrice = gen.header.BaseFee 136 } 137 signer := gen.Signer() 138 for { 139 gas -= params.TxGas 140 if gas < params.TxGas { 141 break 142 } 143 to := (from + 1) % naccounts 144 burn := new(big.Int).SetUint64(params.TxGas) 145 burn.Mul(burn, gen.header.BaseFee) 146 availableFunds.Sub(availableFunds, burn) 147 if availableFunds.Cmp(big.NewInt(1)) < 0 { 148 panic("not enough funds") 149 } 150 tx, err := types.SignNewTx(ringKeys[from], signer, 151 &types.LegacyTx{ 152 Nonce: gen.TxNonce(ringAddrs[from]), 153 To: &ringAddrs[to], 154 Value: availableFunds, 155 Gas: params.TxGas, 156 GasPrice: gasPrice, 157 }) 158 if err != nil { 159 panic(err) 160 } 161 gen.AddTx(tx) 162 from = to 163 } 164 } 165 } 166 167 // genUncles generates blocks with two uncle headers. 168 func genUncles(i int, gen *BlockGen) { 169 if i >= 7 { 170 b2 := gen.PrevBlock(i - 6).Header() 171 b2.Extra = []byte("foo") 172 gen.AddUncle(b2) 173 b3 := gen.PrevBlock(i - 6).Header() 174 b3.Extra = []byte("bar") 175 gen.AddUncle(b3) 176 } 177 } 178 179 func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { 180 // Create the database in memory or in a temporary directory. 181 var db ethdb.Database 182 if !disk { 183 db = rawdb.NewMemoryDatabase() 184 } else { 185 pdb, err := pebble.New(b.TempDir(), 128, 128, "", false) 186 if err != nil { 187 b.Fatalf("cannot create temporary database: %v", err) 188 } 189 db = rawdb.NewDatabase(pdb) 190 defer db.Close() 191 } 192 // Generate a chain of b.N blocks using the supplied block 193 // generator function. 194 gspec := &Genesis{ 195 Config: params.TestChainConfig, 196 Alloc: types.GenesisAlloc{benchRootAddr: {Balance: benchRootFunds}}, 197 } 198 _, chain, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), b.N, gen) 199 200 // Time the insertion of the new chain. 201 // State and blocks are stored in the same DB. 202 chainman, _ := NewBlockChain(db, gspec, ethash.NewFaker(), nil) 203 defer chainman.Stop() 204 b.ReportAllocs() 205 b.ResetTimer() 206 if i, err := chainman.InsertChain(chain); err != nil { 207 b.Fatalf("insert error (block %d): %v\n", i, err) 208 } 209 } 210 211 func BenchmarkChainRead_header_10k(b *testing.B) { 212 benchReadChain(b, false, 10000) 213 } 214 func BenchmarkChainRead_full_10k(b *testing.B) { 215 benchReadChain(b, true, 10000) 216 } 217 func BenchmarkChainRead_header_100k(b *testing.B) { 218 if testing.Short() { 219 b.Skip("Skipping in short-mode") 220 } 221 benchReadChain(b, false, 100000) 222 } 223 func BenchmarkChainRead_full_100k(b *testing.B) { 224 if testing.Short() { 225 b.Skip("Skipping in short-mode") 226 } 227 benchReadChain(b, true, 100000) 228 } 229 func BenchmarkChainRead_header_500k(b *testing.B) { 230 if testing.Short() { 231 b.Skip("Skipping in short-mode") 232 } 233 benchReadChain(b, false, 500000) 234 } 235 func BenchmarkChainRead_full_500k(b *testing.B) { 236 if testing.Short() { 237 b.Skip("Skipping in short-mode") 238 } 239 benchReadChain(b, true, 500000) 240 } 241 func BenchmarkChainWrite_header_10k(b *testing.B) { 242 benchWriteChain(b, false, 10000) 243 } 244 func BenchmarkChainWrite_full_10k(b *testing.B) { 245 benchWriteChain(b, true, 10000) 246 } 247 func BenchmarkChainWrite_header_100k(b *testing.B) { 248 benchWriteChain(b, false, 100000) 249 } 250 func BenchmarkChainWrite_full_100k(b *testing.B) { 251 benchWriteChain(b, true, 100000) 252 } 253 func BenchmarkChainWrite_header_500k(b *testing.B) { 254 if testing.Short() { 255 b.Skip("Skipping in short-mode") 256 } 257 benchWriteChain(b, false, 500000) 258 } 259 func BenchmarkChainWrite_full_500k(b *testing.B) { 260 if testing.Short() { 261 b.Skip("Skipping in short-mode") 262 } 263 benchWriteChain(b, true, 500000) 264 } 265 266 // makeChainForBench writes a given number of headers or empty blocks/receipts 267 // into a database. 268 func makeChainForBench(db ethdb.Database, genesis *Genesis, full bool, count uint64) { 269 var hash common.Hash 270 for n := uint64(0); n < count; n++ { 271 header := &types.Header{ 272 Coinbase: common.Address{}, 273 Number: big.NewInt(int64(n)), 274 ParentHash: hash, 275 Difficulty: big.NewInt(1), 276 UncleHash: types.EmptyUncleHash, 277 TxHash: types.EmptyTxsHash, 278 ReceiptHash: types.EmptyReceiptsHash, 279 } 280 if n == 0 { 281 header = genesis.ToBlock().Header() 282 } 283 hash = header.Hash() 284 285 rawdb.WriteHeader(db, header) 286 rawdb.WriteCanonicalHash(db, hash, n) 287 288 if n == 0 { 289 rawdb.WriteChainConfig(db, hash, genesis.Config) 290 } 291 rawdb.WriteHeadHeaderHash(db, hash) 292 293 if full || n == 0 { 294 block := types.NewBlockWithHeader(header) 295 rawdb.WriteBody(db, hash, n, block.Body()) 296 rawdb.WriteReceipts(db, hash, n, nil) 297 rawdb.WriteHeadBlockHash(db, hash) 298 } 299 } 300 } 301 302 func benchWriteChain(b *testing.B, full bool, count uint64) { 303 genesis := &Genesis{Config: params.AllEthashProtocolChanges} 304 for i := 0; i < b.N; i++ { 305 pdb, err := pebble.New(b.TempDir(), 1024, 128, "", false) 306 if err != nil { 307 b.Fatalf("error opening database: %v", err) 308 } 309 db := rawdb.NewDatabase(pdb) 310 makeChainForBench(db, genesis, full, count) 311 db.Close() 312 } 313 } 314 315 func benchReadChain(b *testing.B, full bool, count uint64) { 316 dir := b.TempDir() 317 318 pdb, err := pebble.New(dir, 1024, 128, "", false) 319 if err != nil { 320 b.Fatalf("error opening database: %v", err) 321 } 322 db := rawdb.NewDatabase(pdb) 323 324 genesis := &Genesis{Config: params.AllEthashProtocolChanges} 325 makeChainForBench(db, genesis, full, count) 326 db.Close() 327 options := DefaultConfig().WithArchive(true) 328 b.ReportAllocs() 329 b.ResetTimer() 330 331 for i := 0; i < b.N; i++ { 332 pdb, err = pebble.New(dir, 1024, 128, "", false) 333 if err != nil { 334 b.Fatalf("error opening database: %v", err) 335 } 336 db = rawdb.NewDatabase(pdb) 337 338 chain, err := NewBlockChain(db, genesis, ethash.NewFaker(), options) 339 if err != nil { 340 b.Fatalf("error creating chain: %v", err) 341 } 342 for n := uint64(0); n < count; n++ { 343 header := chain.GetHeaderByNumber(n) 344 if full { 345 hash := header.Hash() 346 rawdb.ReadBody(db, hash, n) 347 rawdb.ReadReceipts(db, hash, n, header.Time, chain.Config()) 348 } 349 } 350 chain.Stop() 351 db.Close() 352 } 353 }