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  }