Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Block Benchmark Suite #1955

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open

Conversation

RodrigoVillar
Copy link
Contributor

@RodrigoVillar RodrigoVillar commented Mar 5, 2025

This PR adds a benchmarking test suite for benchmarking the processor execution time of a list of blocks. This test suite can be used for benchmarking both the HyperSDK itself and any custom VM which wants to benchmark its own block execution time.

As part of the test suite, users are required to pass in a TxListGenerator which when called, returns a list of transactions that will be set to the transaction list of a block. An implementation of TxListGenerator can be used to benchmark the HyperSDK's pessimistic concurrency model against various state access distributions (i.e. TXs with no conflicting state accesses, TXs with all conflicting state accesses, TXs with state accesses under a Zipf distribution).

An example of how benchmarking would look like for the HyperSDK (with empty blocks) is as follows:

func BenchmarkProcessorExecuteEmptyBlocks(b *testing.B) {
	test := &chaintest.BlockBenchmark{
		MetadataManager: metadata.NewDefaultManager(),
		BalanceHandler:  &mockBalanceHandler{},
		AuthVM: &chaintest.TestAuthVM{
			GetAuthBatchVerifierF: func(authTypeID uint8, cores int, count int) (chain.AuthBatchVerifier, bool) {
				bv, ok := auth.Engines()[authTypeID]
				if !ok {
					return nil, false
				}
				return bv.GetBatchVerifier(cores, count), ok
			},
			Log: logging.NoLog{},
		},
		RuleFactory:      &genesis.ImmutableRuleFactory{Rules: genesis.NewDefaultRules()},
		TxListGenerator:  chaintest.NoopTxListGenerator{},
		Config:           chain.NewDefaultConfig(),
		NumOfBlocks:      10_000,
		NumOfTxsPerBlock: 0,
	}
	test.Run(context.Background(), b)
}

@RodrigoVillar RodrigoVillar marked this pull request as ready for review March 5, 2025 18:55
@RodrigoVillar RodrigoVillar linked an issue Mar 5, 2025 that may be closed by this pull request
Comment on lines 116 to 121
switch numOfTxsPerBlock {
case 0:
timestampOffset = rules.GetMinEmptyBlockGap()
default:
timestampOffset = rules.GetMinBlockGap()
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we handle this with a single if block instead of a switch?

Not identical, but this is a case of "unnecessary else" - https://github.com/uber-go/guide/blob/master/style.md#unnecessary-else

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 258 to 271
factories := []chain.AuthFactory{}
customAllocs := []*genesis.CustomAllocation{}
for range test.NumOfTxsPerBlock {
pk, err := ed25519.GeneratePrivateKey()
r.NoError(err)

factory := auth.NewED25519Factory(pk)
factories = append(factories, factory)

customAllocs = append(customAllocs, &genesis.CustomAllocation{
Address: factory.Address(),
Balance: uint64(allocBalance),
})
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave the auth factories behind the generator interface? This is a VM-level detail that will have a non-trivial performance impact.

Comment on lines +161 to +202
tsv := ts.NewView(state.CompletePermissions, parentView, 0)
if err := tsv.Insert(ctx, chain.HeightKey(metadataManager.HeightPrefix()), binary.BigEndian.AppendUint64(nil, height)); err != nil {
return nil, err
}
if err := tsv.Insert(ctx, chain.TimestampKey(metadataManager.TimestampPrefix()), binary.BigEndian.AppendUint64(nil, uint64(timestamp))); err != nil {
return nil, err
}
if err := tsv.Insert(ctx, chain.FeeKey(metadataManager.FeePrefix()), feeManager.Bytes()); err != nil {
return nil, err
}
tsv.Commit()

root, err := parentView.GetMerkleRoot(ctx)
if err != nil {
return nil, err
}

parentView, err = parentView.NewView(ctx, merkledb.ViewChanges{
MapOps: ts.ChangedKeys(),
ConsumeBytes: true,
})
if err != nil {
return nil, err
}

statelessBlock, err := chain.NewStatelessBlock(
parentID,
timestamp,
height,
txs,
root,
nil,
)
if err != nil {
return nil, err
}
parentID = statelessBlock.GetID()

blk := chain.NewExecutionBlock(statelessBlock)
executionBlocks[i] = blk
}
return executionBlocks, nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is getting duplicated across a growing number of places - can we make this more re-usable?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For DSMR - we need the ability to assemble a block given a parent state / block, metadata for the next block (assigned from DSMR wrapper block), and a set of transactions that should be included. Maybe we can implement an Assembler and use that functionality here

Comment on lines 318 to 334
var view merkledb.View
view = db
b.ResetTimer()
for i := 0; i < b.N; i++ {
for j := 0; j < int(test.NumOfBlocks); j++ {
outputBlock, err := processor.Execute(
ctx,
view,
blocks[j],
false,
)
r.NoError(err)
view = outputBlock.View
}
view = db
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a comment explaining the handling of views here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

ctx,
view,
blocks[j],
false,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use false here? In normal operation, this will be set to true and we will need to check the expiry window

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to true.

Comment on lines 248 to 256
switch test.AuthVerificationCores {
case 0, 1:
processorWorkers = workers.NewSerial()
default:
processorWorkers = workers.NewParallel(
test.AuthVerificationCores,
numOfAuthVerificationJobs,
)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use a simple if condition instead of a switch since there are only two options?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 267 to 271
customAllocs = append(customAllocs, &genesis.CustomAllocation{
Address: factory.Address(),
Balance: uint64(allocBalance),
})
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm - this limits the usability of this package to VMs that use the default genesis / CustomAllocation types.

It seems like the caller should define their application logic including the genesis, transactions (auth/actions), and pass in the components they would have control over as opposed to making this a responsibility of the benchmark tool

Base automatically changed from add-genesis-to-chain to main March 5, 2025 22:43
@RodrigoVillar RodrigoVillar marked this pull request as draft March 6, 2025 16:22
@RodrigoVillar
Copy link
Contributor Author

RodrigoVillar commented Mar 6, 2025

Regarding #1955 (comment) and #1955 (comment), I've decided to switch from users providing a tx-based helper (TxListGenerator) to providing instead a block-based helper (BlockBenchmarkHelper). BlockBenchmarkHelper still produces the TX lists required for GenerateExecutionBlocks() but in addition, it has the method GenerateGenesis which produces the genesis that the benchmark tests will use to generate the genesis commit. I found GenerateGenesis to be an improvement for the following reasons:

  • Since GenerateGenesis returns an interface, we have that the tests are more VM agnostic (i.e. no longer using genesis.DefaultGenesis)
  • In GenerateGenesis, the test user can also instantiate any factories required for GenerateTxList, instead of having the benchmark tests produce the factories on their behalf.

@RodrigoVillar RodrigoVillar self-assigned this Mar 6, 2025
@RodrigoVillar RodrigoVillar marked this pull request as ready for review March 6, 2025 17:16
MetadataManager: metadata.NewDefaultManager(),
BalanceHandler: &mockBalanceHandler{},
AuthVM: &chaintest.TestAuthVM{
GetAuthBatchVerifierF: func(authTypeID uint8, cores int, count int) (chain.AuthBatchVerifier, bool) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it make sense to have a single copy of the GetAuthBatchVerifierF function and use it across these three locations ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to wait until #1960 is merged to address this.

test.Run(context.Background(), b)
}

func BenchmarkExecuteBlocksZipfTxs(b *testing.B) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given that all these benchmarks are almost identical, I'd opt into having a single benchmark containing multiple sub-benchmarks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, switched to using table benchmarks (looks much better as well!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Benchmark Processor
3 participants