Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Test each benchmark case in own #[test] #9860

Merged
13 commits merged into from
Oct 1, 2021

Conversation

ucover
Copy link
Contributor

@ucover ucover commented Sep 24, 2021

Updates the impl_benchmark_test_suite! and extends the benchmarks! macros.
Closes #9110

Syntax extension

It is now possible to use impl_benchmark_test_suite! within the benchmarks! macro.

Example:

benchmarks! {
	dummy {}: {}

	impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test) // ; allowed here
}

Behavioural change

When impl_benchmark_test_suite! is used in a benchmarks! call, it now generates one #[test] function for each bench case.

Old output:

test benchmarking::benchmark_tests::test_benchmarks ... ok

New output:

test benchmarking::bench_sort_vector ... ok
test benchmarking::bench_accumulate_dummy ... ok
test benchmarking::bench_set_dummy_benchmark ... ok

👉 benchmarks! and impl_benchmark_test_suite! stay backwards compatible.

Parallel benchmarks

Since each bench case has its own #[test] function, it is possible to run them in parallel.
A good example is the contracts pallet:

Result with old syntax (after compilation):

cargo test --features=runtime-benchmarks -- --test-threads 16  58,85s user 0,61s system 108% cpu 54,619 total

Result with new syntax (after compilation):

cargo test --features=runtime-benchmarks -- --test-threads 16  65,28s user 0,87s system 264% cpu 24,981 total

Open points

  • I am not sure if I ran all test. Some down-stream tests would be good for the compatibility check.
  • Comments need to be checked for changes.
  • I have not bumped the version.

polkadot address: 13XwRjzNXFGUBwi8uRDU4AgFuTai7rFPRQ9MVA3M7ci8d23r

@cla-bot-2021
Copy link

cla-bot-2021 bot commented Sep 24, 2021

User @ucover, please sign the CLA here.

@ucover ucover changed the title Test each benchmark case its own #[test] Test each benchmark case in own #[test] Sep 28, 2021
@ucover ucover marked this pull request as ready for review September 28, 2021 15:50
@shawntabrizi shawntabrizi added B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D2-notlive 💤 PR contains changes in a runtime directory that is not deployed to a chain that requires an audit. labels Sep 28, 2021
frame/democracy/src/benchmarking.rs Outdated Show resolved Hide resolved
frame/benchmarking/src/lib.rs Outdated Show resolved Hide resolved
frame/benchmarking/src/lib.rs Show resolved Hide resolved
Copy link
Member

@shawntabrizi shawntabrizi left a comment

Choose a reason for hiding this comment

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

thank you, looks good to me besides the comments above

Copy link
Contributor

@gui1117 gui1117 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 to me once doc about new feature is added

@ucover
Copy link
Contributor Author

ucover commented Sep 29, 2021

looks good to me once doc about new feature is added

So how does this work? Where do you want it documented?
@thiolliere

@ucover ucover requested a review from gui1117 September 29, 2021 12:53
@gui1117
Copy link
Contributor

gui1117 commented Sep 29, 2021

looks good to me once doc about new feature is added

So how does this work? Where do you want it documented? @thiolliere

I just mean this #9860 (comment) benchmark have a new feature: improved test suite when impl_benchmark_test_suite is written inside the macro. So that needs to be documented in the rust-doc of the item benchmark: here

/// Construct pallet benchmarks for weighing dispatchables.
///
/// Works around the idea of complexity parameters, named by a single letter (which is usually
/// upper cased in complexity notation but is lower-cased for use in this macro).
///
/// Complexity parameters ("parameters") have a range which is a `u32` pair. Every time a benchmark
/// is prepared and run, this parameter takes a concrete value within the range. There is an
/// associated instancing block, which is a single expression that is evaluated during
/// preparation. It may use `?` (`i.e. `return Err(...)`) to bail with a string error. Here's a
/// few examples:
///
/// ```ignore
/// // These two are equivalent:
/// let x in 0 .. 10;
/// let x in 0 .. 10 => ();
/// // This one calls a setup function and might return an error (which would be terminal).
/// let y in 0 .. 10 => setup(y)?;
/// // This one uses a code block to do lots of stuff:
/// let z in 0 .. 10 => {
/// let a = z * z / 5;
/// let b = do_something(a)?;
/// combine_into(z, b);
/// }
/// ```
///
/// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a
/// literal or constant), then it must be parenthesised.
///
/// The macro allows for a number of "arms", each representing an individual benchmark. Using the
/// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of
/// the benchmark is the same as that of the associated function. However, extended syntax allows
/// for arbitrary expresions to be evaluated in a benchmark (including for example,
/// `on_initialize`).
///
/// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which
/// are left-inclusive right-exclusive.
///
/// Each arm may also have a block of code which is run prior to any instancing and a block of code
/// which is run afterwards. All code blocks may draw upon the specific value of each parameter
/// at any time. Local variables are shared between the two pre- and post- code blocks, but do not
/// leak from the interior of any instancing expressions.
///
/// Example:
/// ```ignore
/// benchmarks! {
/// where_clause { where T::A: From<u32> } // Optional line to give additional bound on `T`.
///
/// // first dispatchable: foo; this is a user dispatchable and operates on a `u8` vector of
/// // size `l`
/// foo {
/// let caller = account::<T>(b"caller", 0, benchmarks_seed);
/// let l in 1 .. MAX_LENGTH => initialize_l(l);
/// }: _(Origin::Signed(caller), vec![0u8; l])
///
/// // second dispatchable: bar; this is a root dispatchable and accepts a `u8` vector of size
/// // `l`.
/// // In this case, we explicitly name the call using `bar` instead of `_`.
/// bar {
/// let l in 1 .. MAX_LENGTH => initialize_l(l);
/// }: bar(Origin::Root, vec![0u8; l])
///
/// // third dispatchable: baz; this is a user dispatchable. It isn't dependent on length like the
/// // other two but has its own complexity `c` that needs setting up. It uses `caller` (in the
/// // pre-instancing block) within the code block. This is only allowed in the param instancers
/// // of arms.
/// baz1 {
/// let caller = account::<T>(b"caller", 0, benchmarks_seed);
/// let c = 0 .. 10 => setup_c(&caller, c);
/// }: baz(Origin::Signed(caller))
///
/// // this is a second benchmark of the baz dispatchable with a different setup.
/// baz2 {
/// let caller = account::<T>(b"caller", 0, benchmarks_seed);
/// let c = 0 .. 10 => setup_c_in_some_other_way(&caller, c);
/// }: baz(Origin::Signed(caller))
///
/// // You may optionally specify the origin type if it can't be determined automatically like
/// // this.
/// baz3 {
/// let caller = account::<T>(b"caller", 0, benchmarks_seed);
/// let l in 1 .. MAX_LENGTH => initialize_l(l);
/// }: baz<T::Origin>(Origin::Signed(caller), vec![0u8; l])
///
/// // this is benchmarking some code that is not a dispatchable.
/// populate_a_set {
/// let x in 0 .. 10_000;
/// let mut m = Vec::<u32>::new();
/// for i in 0..x {
/// m.insert(i);
/// }
/// }: { m.into_iter().collect::<BTreeSet>() }
/// }
/// ```
///
/// Test functions are automatically generated for each benchmark and are accessible to you when you
/// run `cargo test`. All tests are named `test_benchmark_<benchmark_name>`, implemented on the
/// Pallet struct, and run them in a test externalities environment. The test function runs your
/// benchmark just like a regular benchmark, but only testing at the lowest and highest values for
/// each component. The function will return `Ok(())` if the benchmarks return no errors.
///
/// You can optionally add a `verify` code block at the end of a benchmark to test any final state
/// of your benchmark in a unit test. For example:
///
/// ```ignore
/// sort_vector {
/// let x in 1 .. 10000;
/// let mut m = Vec::<u32>::new();
/// for i in (0..x).rev() {
/// m.push(i);
/// }
/// }: {
/// m.sort();
/// } verify {
/// ensure!(m[0] == 0, "You forgot to sort!")
/// }
/// ```
///
/// These `verify` blocks will not affect your benchmark results!
///
/// You can construct benchmark tests like so:
///
/// ```ignore
/// #[test]
/// fn test_benchmarks() {
/// new_test_ext().execute_with(|| {
/// assert_ok!(Pallet::<Test>::test_benchmark_dummy());
/// assert_err!(Pallet::<Test>::test_benchmark_other_name(), "Bad origin");
/// assert_ok!(Pallet::<Test>::test_benchmark_sort_vector());
/// assert_err!(Pallet::<Test>::test_benchmark_broken_benchmark(), "You forgot to sort!");
/// });
/// }
/// ```
#[macro_export]
macro_rules! benchmarks {

@ucover ucover requested a review from gui1117 September 30, 2021 13:29
@gui1117
Copy link
Contributor

gui1117 commented Oct 1, 2021

I wonder if it would be difficult to do a test that ensures those test are correctly generated, maybe by ensuring they fail somewhere, anyway it is fine for now.

@gui1117
Copy link
Contributor

gui1117 commented Oct 1, 2021

bot merge

@ghost
Copy link

ghost commented Oct 1, 2021

Trying merge.

@ghost ghost merged commit 3d0a917 into paritytech:master Oct 1, 2021
@KiChjang
Copy link
Contributor

KiChjang commented Oct 1, 2021

Sorry I was late into the game, but I think a follow-up to this would be to figure out a nicer syntax for the impl_benchmark_test_suite macro. Since parsing and expanding the macro falls within the responsibility of benchmarks!, we don't actually need to keep macro syntax for impl_benchmark_test_suite anymore, and can think of a more intuitive one or even a less verbose one for it.

ordian added a commit that referenced this pull request Oct 2, 2021
* master: (67 commits)
  Downstream `node-template` pull (#9915)
  Implement core::fmt::Debug for BoundedVec (#9914)
  Quickly skip invalid transactions during block authorship. (#9789)
  Add SS58 prefix for Automata (#9805)
  Clean up sc-peerset (#9806)
  Test each benchmark case in own #[test] (#9860)
  Add build with docker section to README (#9792)
  Simple Trait to Inspect Metadata (#9893)
  Pallet Assets: Create new asset classes from genesis config (#9742)
  doc: subkey usage (#9905)
  Silence alert about large-statement-fetcher (#9882)
  Fix democracy on-initialize weight (#9890)
  Fix basic authorship flaky test (#9906)
  contracts: Add event field names (#9896)
  subkey readme update on install (#9900)
  add feature wasmtime-jitdump (#9871)
  Return `target_hash` for finality_target instead of an Option (#9867)
  Update wasmtime to 0.29.0 (#9552)
  Less sleeps (#9848)
  remove unidiomatic (#9895)
  ...
@gui1117
Copy link
Contributor

gui1117 commented Oct 6, 2021

we could also make it an attribute macro so that people can have the automatic formatting

@shawntabrizi
Copy link
Member

/tip medium

@substrate-tip-bot
Copy link

Please fix the following problems before calling the tip bot again:

  • Contributor did not properly post their Polkadot or Kusama address. Make sure the pull request has: "{network} address: {address}".

@ucover
Copy link
Contributor Author

ucover commented Oct 6, 2021

Nice bot! Thanks very much 😄

I added my polkadot address to the MR description.
@shawntabrizi @substrate-tip-bot

@athei
Copy link
Member

athei commented Oct 6, 2021

You deserve it. That was bugging me for some time.

@shawntabrizi
Copy link
Member

/tip medium

@substrate-tip-bot
Copy link

A medium tip was successfully submitted for ucover (13XwRjzNXFGUBwi8uRDU4AgFuTai7rFPRQ9MVA3M7ci8d23r on polkadot).

https://polkadot.js.org/apps/#/treasury/tips

niklasad1 added a commit that referenced this pull request Oct 8, 2021
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D2-notlive 💤 PR contains changes in a runtime directory that is not deployed to a chain that requires an audit.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

improve impl_benchmark_test_suite so that it generate a test for each benchmarks
5 participants