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

cmd/evm: benchmarking via statetest command + filter by name, index and fork #30442

Merged
merged 13 commits into from
Nov 8, 2024

Conversation

jwasinger
Copy link
Contributor

When evm statetest --bench is specified, benchmark the execution similarly to evm run. It will explicitly error if there is more than one matched subtest to be executed (new statetest flags are added to filter which tests are ran).

Breaks the state test runner API. But I figure that we are the only consumers of this API, so it's not a big problem?

Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

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

Checked to see if there was any noticeable slowdown.

Master:

$ time yes ~/go/src/github.com/holiman/goevmlab/examples/sstore_static/sstore_test.json | head -n 1000 | go run ./cmd/evm/ statetest 2>/dev/null 1>/dev/null 

real    0m3.389s
user    0m3.688s
sys     0m1.008s

This PR

$ time yes ~/go/src/github.com/holiman/goevmlab/examples/sstore_static/sstore_test.json | head -n 1000 | go run ./cmd/evm/ statetest 2>/dev/null 1>/dev/null 

real    0m3.409s
user    0m3.927s
sys     0m1.042s

So 👍


Just some minor nits, otherwise lgtm

cmd/evm/staterunner.go Outdated Show resolved Hide resolved
cmd/evm/staterunner.go Outdated Show resolved Hide resolved
cmd/evm/staterunner.go Show resolved Hide resolved
@holiman holiman changed the title tests, cmd/evm: expand --bench flag support to include statetest subcommand. introduce test name, fork, index filtering flags for state test subcommand. cmd/evm: benchmarking via statetest command + filter by name, index and fork Sep 24, 2024
cmd/evm/staterunner.go Outdated Show resolved Hide resolved
Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

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

LGTM

Comment on lines 187 to 207
} else if len(matchingTests) != 1 {
return fmt.Errorf("can only benchmark single state test case (more than one matching params)")
}
var gasUsed uint64
result := testing.Benchmark(func(b *testing.B) {
for i := 0; i < b.N; i++ {
test := matchingTests[0]
_, _, gasUsed, _ = test.test.RunNoVerify(test.st, cfg, false, rawdb.HashScheme)
}
})
var stats execStats
// Get the average execution time from the benchmarking result.
// There are other useful stats here that could be reported.
stats.time = time.Duration(result.NsPerOp())
stats.allocs = result.AllocsPerOp()
stats.bytesAllocated = result.AllocedBytesPerOp()
fmt.Fprintf(os.Stderr, `EVM gas used: %d
execution time: %v
allocations: %d
allocated bytes: %d
`, gasUsed, stats.time, stats.allocs, stats.bytesAllocated)
Copy link
Member

Choose a reason for hiding this comment

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

I would move this into its own function and do something like this

if bench {
    runBenchmark()
}
return nil

which makes it a big cleaner imo

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's ok as is

Copy link
Member

@MariusVanDerWijden MariusVanDerWijden left a comment

Choose a reason for hiding this comment

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

LGTM, one tiny nitpick that you could ignore if you want

cmd/evm/staterunner.go Outdated Show resolved Hide resolved
Comment on lines 187 to 207
} else if len(matchingTests) != 1 {
return fmt.Errorf("can only benchmark single state test case (more than one matching params)")
}
var gasUsed uint64
result := testing.Benchmark(func(b *testing.B) {
for i := 0; i < b.N; i++ {
test := matchingTests[0]
_, _, gasUsed, _ = test.test.RunNoVerify(test.st, cfg, false, rawdb.HashScheme)
}
})
var stats execStats
// Get the average execution time from the benchmarking result.
// There are other useful stats here that could be reported.
stats.time = time.Duration(result.NsPerOp())
stats.allocs = result.AllocsPerOp()
stats.bytesAllocated = result.AllocedBytesPerOp()
fmt.Fprintf(os.Stderr, `EVM gas used: %d
execution time: %v
allocations: %d
allocated bytes: %d
`, gasUsed, stats.time, stats.allocs, stats.bytesAllocated)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's ok as is

if !bench {
return nil
} else if len(matchingTests) != 1 {
return fmt.Errorf("can only benchmark single state test case (more than one matching params)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why though?
Wouldn't it make more sense to put the benchmarking into the loop above, and after each test.test.Run, you do a bench run, if so desired.
You might even consider integrating the bench stats into the results, but now I'm just thinking aloud... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Benchmark every test unless otherwise-specified sounds like fine default behavior to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

SO drop this whole if-clause?

@holiman
Copy link
Contributor

holiman commented Sep 30, 2024

Sorry -- I know I approved it earlier, and I came around to merge it now, but then I found some more things that could be improved :)

@jwasinger
Copy link
Contributor Author

Tried to re-run the exact test you posted from goevmlab: ~/go/src/github.com/holiman/goevmlab/examples/sstore_static/sstore_test.json but it appears that it isn't in the repo.

Running a separate test shows no measurable performance difference between the latest changes and master branch.

time time.Duration // The execution time.
allocs int64 // The number of heap allocations during execution.
bytesAllocated int64 // The cumulative number of bytes allocated during execution.
type ExecStats struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to be exported? I don't see why

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah I think I got confused because non-exported members of structs are not serialized in JSON so I assumed that the type also had to be exported 🤷‍♂️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wait but how can StateTestResult have a public field with a private type...

if !bench {
return nil
} else if len(matchingTests) != 1 {
return fmt.Errorf("can only benchmark single state test case (more than one matching params)")
Copy link
Contributor

Choose a reason for hiding this comment

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

SO drop this whole if-clause?

cmd/evm/runner.go Outdated Show resolved Hide resolved
Copy link
Contributor

@holiman holiman left a comment

Choose a reason for hiding this comment

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

LGTM

@holiman holiman added this to the 1.14.12 milestone Nov 8, 2024
@holiman holiman merged commit d42d450 into ethereum:master Nov 8, 2024
2 of 3 checks passed
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.

3 participants