github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/manager_benchmark_test.go (about) 1 package computation 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "testing" 8 "time" 9 10 blockstore "github.com/ipfs/boxo/blockstore" 11 "github.com/ipfs/go-datastore" 12 dssync "github.com/ipfs/go-datastore/sync" 13 "github.com/onflow/cadence/runtime" 14 "github.com/rs/zerolog" 15 "github.com/stretchr/testify/mock" 16 "github.com/stretchr/testify/require" 17 18 "github.com/onflow/flow-go/engine/execution/computation/committer" 19 "github.com/onflow/flow-go/engine/execution/computation/computer" 20 "github.com/onflow/flow-go/engine/execution/testutil" 21 "github.com/onflow/flow-go/fvm" 22 reusableRuntime "github.com/onflow/flow-go/fvm/runtime" 23 "github.com/onflow/flow-go/fvm/storage/derived" 24 "github.com/onflow/flow-go/fvm/storage/snapshot" 25 "github.com/onflow/flow-go/model/flow" 26 "github.com/onflow/flow-go/module/executiondatasync/execution_data" 27 exedataprovider "github.com/onflow/flow-go/module/executiondatasync/provider" 28 mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock" 29 "github.com/onflow/flow-go/module/mempool/entity" 30 "github.com/onflow/flow-go/module/metrics" 31 module "github.com/onflow/flow-go/module/mock" 32 requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest" 33 "github.com/onflow/flow-go/module/trace" 34 "github.com/onflow/flow-go/utils/unittest" 35 ) 36 37 type testAccount struct { 38 address flow.Address 39 privateKey flow.AccountPrivateKey 40 } 41 42 type testAccounts struct { 43 accounts []testAccount 44 seq uint64 45 } 46 47 func createAccounts( 48 b *testing.B, 49 vm fvm.VM, 50 snapshotTree snapshot.SnapshotTree, 51 num int, 52 ) ( 53 snapshot.SnapshotTree, 54 *testAccounts, 55 ) { 56 privateKeys, err := testutil.GenerateAccountPrivateKeys(num) 57 require.NoError(b, err) 58 59 snapshotTree, addresses, err := testutil.CreateAccounts( 60 vm, 61 snapshotTree, 62 privateKeys, 63 chain) 64 require.NoError(b, err) 65 66 accs := &testAccounts{ 67 accounts: make([]testAccount, num), 68 } 69 for i := 0; i < num; i++ { 70 accs.accounts[i] = testAccount{ 71 address: addresses[i], 72 privateKey: privateKeys[i], 73 } 74 } 75 return snapshotTree, accs 76 } 77 78 func mustFundAccounts( 79 b *testing.B, 80 vm fvm.VM, 81 snapshotTree snapshot.SnapshotTree, 82 execCtx fvm.Context, 83 accs *testAccounts, 84 ) snapshot.SnapshotTree { 85 var err error 86 for _, acc := range accs.accounts { 87 transferTx := testutil.CreateTokenTransferTransaction(chain, 1_000_000, acc.address, chain.ServiceAddress()) 88 err = testutil.SignTransactionAsServiceAccount(transferTx, accs.seq, chain) 89 require.NoError(b, err) 90 accs.seq++ 91 92 executionSnapshot, output, err := vm.Run( 93 execCtx, 94 fvm.Transaction(transferTx, 0), 95 snapshotTree) 96 require.NoError(b, err) 97 require.NoError(b, output.Err) 98 snapshotTree = snapshotTree.Append(executionSnapshot) 99 } 100 101 return snapshotTree 102 } 103 104 func BenchmarkComputeBlock(b *testing.B) { 105 b.StopTimer() 106 b.SetParallelism(1) 107 108 type benchmarkCase struct { 109 numCollections int 110 numTransactionsPerCollection int 111 maxConcurrency int 112 } 113 114 for _, benchCase := range []benchmarkCase{ 115 { 116 numCollections: 16, 117 numTransactionsPerCollection: 128, 118 maxConcurrency: 1, 119 }, 120 { 121 numCollections: 16, 122 numTransactionsPerCollection: 128, 123 maxConcurrency: 2, 124 }, 125 } { 126 b.Run( 127 fmt.Sprintf( 128 "%d/cols/%d/txes/%d/max-concurrency", 129 benchCase.numCollections, 130 benchCase.numTransactionsPerCollection, 131 benchCase.maxConcurrency), 132 func(b *testing.B) { 133 benchmarkComputeBlock( 134 b, 135 benchCase.numCollections, 136 benchCase.numTransactionsPerCollection, 137 benchCase.maxConcurrency) 138 }) 139 } 140 } 141 142 func benchmarkComputeBlock( 143 b *testing.B, 144 numCollections int, 145 numTransactionsPerCollection int, 146 maxConcurrency int, 147 ) { 148 tracer, err := trace.NewTracer(zerolog.Nop(), "", "", 4) 149 require.NoError(b, err) 150 151 vm := fvm.NewVirtualMachine() 152 153 const chainID = flow.Emulator 154 execCtx := fvm.NewContext( 155 fvm.WithChain(chainID.Chain()), 156 fvm.WithAccountStorageLimit(true), 157 fvm.WithTransactionFeesEnabled(true), 158 fvm.WithTracer(tracer), 159 fvm.WithReusableCadenceRuntimePool( 160 reusableRuntime.NewReusableCadenceRuntimePool( 161 ReusableCadenceRuntimePoolSize, 162 runtime.Config{}, 163 )), 164 ) 165 snapshotTree := testutil.RootBootstrappedLedger( 166 vm, 167 execCtx, 168 fvm.WithAccountCreationFee(fvm.DefaultAccountCreationFee), 169 fvm.WithMinimumStorageReservation(fvm.DefaultMinimumStorageReservation), 170 fvm.WithTransactionFee(fvm.DefaultTransactionFees), 171 fvm.WithStorageMBPerFLOW(fvm.DefaultStorageMBPerFLOW), 172 ) 173 snapshotTree, accs := createAccounts(b, vm, snapshotTree, 1000) 174 snapshotTree = mustFundAccounts(b, vm, snapshotTree, execCtx, accs) 175 176 me := new(module.Local) 177 me.On("NodeID").Return(flow.ZeroID) 178 me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil) 179 me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). 180 Return(nil, nil) 181 182 bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore()))) 183 trackerStorage := mocktracker.NewMockStorage() 184 185 prov := exedataprovider.NewProvider( 186 zerolog.Nop(), 187 metrics.NewNoopCollector(), 188 execution_data.DefaultSerializer, 189 bservice, 190 trackerStorage, 191 ) 192 193 // TODO(rbtz): add real ledger 194 blockComputer, err := computer.NewBlockComputer( 195 vm, 196 execCtx, 197 metrics.NewNoopCollector(), 198 tracer, 199 zerolog.Nop(), 200 committer.NewNoopViewCommitter(), 201 me, 202 prov, 203 nil, 204 testutil.ProtocolStateWithSourceFixture(nil), 205 maxConcurrency) 206 require.NoError(b, err) 207 208 derivedChainData, err := derived.NewDerivedChainData( 209 derived.DefaultDerivedDataCacheSize) 210 require.NoError(b, err) 211 212 engine := &Manager{ 213 blockComputer: blockComputer, 214 derivedChainData: derivedChainData, 215 } 216 217 parentBlock := &flow.Block{ 218 Header: &flow.Header{}, 219 Payload: &flow.Payload{}, 220 } 221 222 b.StopTimer() 223 b.ResetTimer() 224 225 var elapsed time.Duration 226 for i := 0; i < b.N; i++ { 227 executableBlock := createBlock( 228 b, 229 parentBlock, 230 accs, 231 numCollections, 232 numTransactionsPerCollection) 233 parentBlock = executableBlock.Block 234 235 b.StartTimer() 236 start := time.Now() 237 res, err := engine.ComputeBlock( 238 context.Background(), 239 unittest.IdentifierFixture(), 240 executableBlock, 241 snapshotTree) 242 elapsed += time.Since(start) 243 b.StopTimer() 244 245 require.NoError(b, err) 246 for _, snapshot := range res.AllExecutionSnapshots() { 247 snapshotTree = snapshotTree.Append(snapshot) 248 } 249 250 for j, r := range res.AllTransactionResults() { 251 // skip system transactions 252 if j >= numCollections*numTransactionsPerCollection { 253 break 254 } 255 require.Emptyf(b, r.ErrorMessage, "Transaction %d failed", j) 256 } 257 } 258 totalTxes := int64(numCollections) * int64(numTransactionsPerCollection) * int64(b.N) 259 b.ReportMetric(float64(elapsed.Nanoseconds()/totalTxes/int64(time.Microsecond)), "us/tx") 260 } 261 262 func createBlock(b *testing.B, parentBlock *flow.Block, accs *testAccounts, colNum int, txNum int) *entity.ExecutableBlock { 263 completeCollections := make(map[flow.Identifier]*entity.CompleteCollection, colNum) 264 collections := make([]*flow.Collection, colNum) 265 guarantees := make([]*flow.CollectionGuarantee, colNum) 266 267 for c := 0; c < colNum; c++ { 268 transactions := make([]*flow.TransactionBody, txNum) 269 for t := 0; t < txNum; t++ { 270 transactions[t] = createTokenTransferTransaction(b, accs) 271 } 272 273 collection := &flow.Collection{Transactions: transactions} 274 guarantee := &flow.CollectionGuarantee{CollectionID: collection.ID()} 275 276 collections[c] = collection 277 guarantees[c] = guarantee 278 completeCollections[guarantee.ID()] = &entity.CompleteCollection{ 279 Guarantee: guarantee, 280 Transactions: transactions, 281 } 282 } 283 284 block := flow.Block{ 285 Header: &flow.Header{ 286 ParentID: parentBlock.ID(), 287 View: parentBlock.Header.Height + 1, 288 }, 289 Payload: &flow.Payload{ 290 Guarantees: guarantees, 291 }, 292 } 293 294 return &entity.ExecutableBlock{ 295 Block: &block, 296 CompleteCollections: completeCollections, 297 StartState: unittest.StateCommitmentPointerFixture(), 298 } 299 } 300 301 func createTokenTransferTransaction(b *testing.B, accs *testAccounts) *flow.TransactionBody { 302 var err error 303 304 rnd := rand.Intn(len(accs.accounts)) 305 src := accs.accounts[rnd] 306 dst := accs.accounts[(rnd+1)%len(accs.accounts)] 307 308 tx := testutil.CreateTokenTransferTransaction(chain, 1, dst.address, src.address) 309 tx.SetProposalKey(chain.ServiceAddress(), 0, accs.seq). 310 SetComputeLimit(1000). 311 SetPayer(chain.ServiceAddress()) 312 accs.seq++ 313 314 err = testutil.SignPayload(tx, src.address, src.privateKey) 315 require.NoError(b, err) 316 317 err = testutil.SignEnvelope(tx, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey) 318 require.NoError(b, err) 319 320 return tx 321 }