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

feat: gnovm benchmarking tool #2241

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

Conversation

piux2
Copy link
Contributor

@piux2 piux2 commented May 30, 2024

Contributors' checklist...
  • [ x] Added new tests, or not needed, or not feasible
  • [ x] Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory
  • [ x] Updated the official documentation or not needed
  • [ x] No breaking changes were made, or a BREAKING CHANGE: xxx message was included in the description
  • [ x] Added references to related issues and PRs
  • [ x] Provided any useful hints for running manual tests
  • Added new benchmarks to generated graphs, if any. More info here.

We build this tool mainly for the following issues

#1826
#1828
#1281
#1973

We could also use it in the following cases.

#1973
#2222

gnobench benchmarks the time consumed for each VM CPU OpCode and persistent access to the store, including marshalling and unmarshalling of realm objects.

Design consideration

Minimum Overhead and Footprint

  • Constant build flags enable benchmarking.
  • Encode operations and measurements in binary.
  • Dump to a local file in binary.
  • No logging, printout, or network access involved.

Accuracy

  • Pause the timer for storage access while performing VM opcode benchmarking.
  • Measure each OpCode execution in nanoseconds.
  • Store access includes the duration for Amino marshalling and unmarshalling.

It is built on top of @deelawn's design and framework with @jaekwon's input.
#2073

Usage

Simple mode

The benchmark only involves the GnoVM and the persistent store. It benchmarks the bare minimum components, and the results are isolated from other components. We use standardize gno contract to perform the benchmarking.

This mode is the best for benchmarking each major release and/or changes in GnoVM.

make opcode
make storage

Production mode

It benchmarks the node in the production environment with minimum overhead.
We can only benchmark with standardize the contract but also capture the live usage in production environment.
It gives us a complete picture of the node perform.

  1. Build the production node with benchmarking flags:

go build -tags "benchmarkingstorage benchmarkingops" gno.land/cmd/gnoland

  1. Run the node in the production environment. It will dump benchmark data to a benchmark.bin file.

  2. call the realm contracts at gno.land/r/x/benchmark/opcodes and gno.land/r/x/benchmark/storage

  3. Stop the server after the benchmarking session is complete.

  4. Run the following command to convert the binary dump:

gnobench -bin path_to_benchmark_bin

it converts the binary dump to results.csv and results_stats.csv.

Results ( Examples )

The benchmarking results are stored in two files:

  1. The raw results are saved in results.csv.
Operation Elapsed Time Disk IO Bytes
OpEval 40333 0
OpPopBlock 208 0
OpHalt 167 0
OpEval 500 0
OpInterfaceType 458 0
OpPopBlock 166 0
OpHalt 125 0
OpInterfaceType 21125 0
OpEval 541 0
OpEval 209 0
OpInterfaceType 334 0
  1. The averages and standard deviations are summarized in results_stats.csv.
Operation Avg Time Avg Size Time Std Dev Count
OpAdd 101 0 45 300
OpAddAssign 309 0 1620 100
OpArrayLit 242 0 170 700
OpArrayType 144 0 100 714
OpAssign 136 0 95 2900
OpBand 92 0 30 100
OpBandAssign 127 0 62 100
OpBandn 97 0 54 100
OpBandnAssign 125 0 113 100
OpBinary1 128 0 767 502
OpBody 127 0 145 13700

@piux2 piux2 requested review from jaekwon, moul, deelawn, zivkovicmilos and a team as code owners May 30, 2024 08:34
@github-actions github-actions bot added 🧾 package/realm Tag used for new Realms or Packages. 📦 🤖 gnovm Issues or PRs gnovm related 📦 ⛰️ gno.land Issues or PRs gno.land package related labels May 30, 2024
Copy link

codecov bot commented May 30, 2024

Codecov Report

Attention: Patch coverage is 16.53061% with 409 lines in your changes missing coverage. Please review.

Project coverage is 60.46%. Comparing base (a73cb22) to head (6c0f085).
Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
gnovm/pkg/benchops/cmd/stats.go 0.00% 105 Missing ⚠️
gnovm/pkg/gnolang/store.go 15.04% 76 Missing and 20 partials ⚠️
gnovm/pkg/benchops/bench.go 0.00% 53 Missing ⚠️
gnovm/pkg/benchops/exporter.go 0.00% 50 Missing ⚠️
gnovm/pkg/gnolang/machine.go 12.90% 20 Missing and 7 partials ⚠️
gnovm/pkg/benchops/cmd/run.go 63.23% 24 Missing and 1 partial ⚠️
gnovm/pkg/benchops/cmd/main.go 0.00% 23 Missing ⚠️
gnovm/pkg/benchops/cmd/store.go 65.38% 6 Missing and 3 partials ⚠️
gnovm/pkg/benchops/ops.go 0.00% 8 Missing ⚠️
gnovm/pkg/gnolang/realm.go 0.00% 6 Missing and 2 partials ⚠️
... and 2 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2241      +/-   ##
==========================================
- Coverage   61.10%   60.46%   -0.64%     
==========================================
  Files         564      572       +8     
  Lines       75355    79491    +4136     
==========================================
+ Hits        46045    48066    +2021     
- Misses      25945    27822    +1877     
- Partials     3365     3603     +238     
Flag Coverage Δ
contribs/gnodev 61.46% <ø> (ø)
contribs/gnofaucet 15.31% <ø> (ø)
gno.land 67.92% <ø> (ø)
gnovm 65.31% <16.59%> (-0.86%) ⬇️
misc/genstd 80.54% <ø> (ø)
misc/logos 19.88% <ø> (-0.36%) ⬇️
tm2 62.10% <ø> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@deelawn deelawn left a comment

Choose a reason for hiding this comment

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

Looks good. Perhaps we could move the transient size variable so that is defined inside the benchmarking package as an exported variable. Then it can be set and read strictly within benchmarking constant conditional blocks and avoid any unnecessary allocations when benchmarking is not enabled.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

Could this be reorganised to:

  • gnovm/cmd/gnobench + gnovm/pkg/gasbench OR
  • contribs/gnobecnh?

I don't want to add another top-level directory.

Copy link
Contributor

Choose a reason for hiding this comment

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

gnovm/pkg/opsbench

@jaekwon
Copy link
Contributor

jaekwon commented Jun 9, 2024

Waiting for updates as per discussions with Ray

@Kouteki Kouteki added the in focus Core team is prioritizing this work label Oct 18, 2024
@zivkovicmilos zivkovicmilos added this to the 🚀 Mainnet launch milestone Oct 28, 2024
@piux2 piux2 mentioned this pull request Oct 31, 2024
6 tasks
@Kouteki Kouteki requested review from sw360cab and removed request for a team November 1, 2024 16:35
@Kouteki Kouteki requested review from mvertes and ltzmaxwell and removed request for moul, zivkovicmilos and sw360cab November 15, 2024 09:31
Copy link
Contributor

@ltzmaxwell ltzmaxwell left a comment

Choose a reason for hiding this comment

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

preliminary review.


// StopMeasurement ends the current measurement and resumes the previous one
// if one exists. It accepts the number of bytes that were read/written to/from
// the store. This value is zero if the operation is not a read or write.
Copy link
Contributor

Choose a reason for hiding this comment

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

the comment seems incorrect

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch!

Comment on lines +576 to +580
if bm.OpsEnabled {
bm.PauseOpCode()
defer bm.ResumeOpCode()
}

Copy link
Contributor

Choose a reason for hiding this comment

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

metric storage 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.

This was purposely left out. GetType(id) is part of a recursive call flow through the realm's fillType() function. To provide an accurate benchmarking measurement, we moved away from the stack-based measurement approach, which involved pushing and pausing a timer on the stack when the runtime entered a function call. The trade-off is that we can no longer measure the runtime of functions involved in recursive calls. For now, we can assume and assign the same gas value to GetType as we do for SetType.

panic("Can not stop a stopped timer")
}
measure.opAccumDur[code] += time.Since(measure.opStartTime[code])
measure.opStartTime[code] = measure.timeZero // stop the timer
Copy link
Contributor

Choose a reason for hiding this comment

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

unset isOpCodeStarted?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch! I will fix it.

@zivkovicmilos
Copy link
Member

Hey @piux2, can you rebase this with master? 🙏

Copy link
Contributor

@mvertes mvertes left a comment

Choose a reason for hiding this comment

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

The pr provides:

  1. Performance measurement capabilities for opcodes (count, execution time) and persistent storage (count, size). This part is similar to performance counter registers in processors. It would better be called perf (the VM capability) instead of benchops (a particular usage of this capability, and not the only possible one, i.e profiling, gas measurement). See for example linux or freebsd

  2. An executable called gnobench, to perform a benchmark on a specific scenario (a sample forum board in gno). This executable should not be located under gnovm, but in contribs/benchmark, with
    its dependencies:

    • the readme, makefile, etc in contrib/benchmark
    • the executable itself in contrib/benchmark/cmd/gnobench
    • benchmarked gno files in contrib/benchmark/gno
    • the vm part still in gnovm/pkg/perf

Regarding the build tags to enable or not the counting, I'm not a big fan of that. It brings a lot of hassle, and it's partly useless because counting is necessary in production anyway. The performance gain vs using a variable instead of a constant (requiring a different executable) to enable the feature in opcode procressing was shown negligible when we introduced the debugger, it should be the same here. A former colleague of mine used to say: "Premature optimisation is the root of all evil" :-)

In more details, the measurement of opcodes consists of counting and measuring execution time. Counting is super cheap (a counter increment operation), and could be always performed. Measuring execution time is probably a bit more costly and it makes sense to enable it only for benchmark scenarios. In production, we could have a static pre-computed profile of mean exec time per opcode, for gas estimation. But again, no sufficient reason to use build tags.

Regarding the storage of performance counters, they should be stored per virtual machine, instead of a global variable measure, which is generally a bad practice, bringing a lot of unstated constraints and risks. I understand that it is already the case for other parts of the VM, and that it facilitates the coding, but this is something that we should eventually fix. It can be done later, I don't want to block the PR for that.

@Gno2D2
Copy link

Gno2D2 commented Dec 2, 2024

I'm a bot that assists the Gno Core team in maintaining this repository. My role is to ensure that contributors understand and follow our guidelines, helping to streamline the development process.

The following requirements must be fulfilled before a pull request can be merged.
Some requirement checks are automated and can be verified by the CI, while others need manual verification by a staff member.

These requirements are defined in this configuration file.

Automated Checks

🟢 Maintainers must be able to edit this pull request
🔴 The pull request head branch must be up-to-date with its base

Manual Checks

No manual checks match this pull request.

Debug
Automated Checks
Maintainers must be able to edit this pull request

If

🟢 Condition met
└── 🟢 On every pull request

Then

🟢 Requirement satisfied
└── 🟢 Maintainer can modify this pull request

The pull request head branch must be up-to-date with its base

If

🟢 Condition met
└── 🟢 On every pull request

Then

🔴 Requirement not satisfied
└── 🔴 Head branch (piux2:feat_gnobench) is up to date with base (master): behind by 128 / ahead by 18

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in focus Core team is prioritizing this work 📦 ⛰️ gno.land Issues or PRs gno.land package related 📦 🤖 gnovm Issues or PRs gnovm related 🧾 package/realm Tag used for new Realms or Packages.
Projects
Status: In Progress
Status: In Review
Development

Successfully merging this pull request may close these issues.

9 participants