substrate
substrate copied to clipboard
benchmarking macro overhaul
Description
This PR introduces a new, simplified, and easier-to-use syntax for defining benchmarks that is based on a proc macro instead of a macro_rules!
macro like the current system.
Right now the syntax looks like this:
#[benchmarks]
mod benchmarks {
use super::*;
...
#[benchmark]
fn transfer_increasing_users(u: Linear<0, 1_000>) {
let existential_deposit = T::ExistentialDeposit::get();
let caller = whitelisted_caller();
let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into());
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&caller, balance);
let recipient: T::AccountId = account("recipient", 0, SEED);
let recipient_lookup = T::Lookup::unlookup(recipient.clone());
let transfer_amount = existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into();
for i in 0..u {
let new_user: T::AccountId = account("new_user", i, SEED);
let _ = <Balances<T, I> as Currency<_>>::make_free_balance_be(&new_user, balance);
}
#[extrinsic_call]
transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount);
assert_eq!(Balances::<T, I>::free_balance(&caller), Zero::zero());
assert_eq!(Balances::<T, I>::free_balance(&recipient), transfer_amount);
}
...
}
Syntax
The #[benchmarks]
macro expects a module containing any number of benchmark puesdo function definitions such as the one shown above to be provided in a block. These function definitions should be annotated with #[benchmark]
. A where clause can be provided as an optional argument i.e. #[benchmarks(where <clause here>)]
.
Each benchmark function definition should be zero arg, or should specify one or more named parameters (that meets the requirements of frame_benchmarking::BenchmarkParameter with a type that implements frame_support::benchmarking::ParamRange
. Right now the only struct implementing this trait is Linear
, which has two generic u32
parameters`:
pub struct Linear<const A: u32, const B: u32>;
These generic parameters represent the (inclusive) start and end range of the benchmark parameter. So if you want to specify a range of 1 to 100,000 (inclusive) for the variable x
, you would use:
#[benchmark]
fn my_benchmark(x: Linear<1, 100_000>) {
As mentioned before, you can also specify multiple parameters if necessary:
#[benchmark]
fn my_benchmark(x: Linear<100, 500>, y: Linear<200, 600>) {
These benchmark function definitions can be optionally annotated with the extra
and skip_meta
i.e. #[benchmark(skip_meta, extra)]
and these function the same way they used to function when they were attributes in the old benchmarking syntax
Note that while these benchmark function definitions appear to be function definitions, currently they are not compiled as such (mainly because the signature would be incorrect with respect to the benchmark parameters). If generating a function that resembles the benchmark function definition is desirable, I could easily add this by changing the signature at compile time to transform things like x: Linear<0, 10>
to x: u32
. If this would be of practical use beyond the structs and trait impls already created by the macro then let me know!
The #[extrinsic_call]
acts as a bisector between the setup code, the extrinsic call, and any verification code that needs to go at the end of the benchmark. The extrinsic call can be either block style (pass a block to #[extrinsic_call]
), or must be a function call do an extrinsic where the first argument is the origin.
UPDATE: now we use #[block]
for the block-style call, and #[extrinsic_call]
for an actual extrinsic call.
Instance Benchmarks
You can specify #[instance_benchmarks]
in place of #[benchmarks]
if your pallet requires this.
Outer Macro Pattern
The new setup uses proc macros and the outer macro pattern, similar to the way pallets themselves are defined, with the caveat that individual benchmark function definitions can technically be parsed and expanded on their own -- the only reason we need the outer macro pattern is once all benchmarks within a benchmarks! {}
block have been processed, we need to iterate over them and implement some traits such as run_benchmarks
that require knowledge of all of the benchmarks, so this is why we need the outer macro pattern.
Status
- [X] parsing for new
#[benchmark]
attribute - [X] parsing "bisection" technique for picking out the
#[extrinsic_call]
and using this to separate the setup code from the verification code - [X] new
ParamRange
trait and a genericLinear
struct that implements it - [X] parsing support for
ParamRange
- [X] parsing for new
benchmarks! {}
outer macro - [X] basic expansion for
#[benchmark]
functions - [X] expansion of
benchmarks! {}
macro - [X] conversion of the Balances pallet to the new syntax
- [X] decide on syntax for specifying that we want to code-gen benchmark tests (previously was
impl_benchmark_test_suite!
, ~~maybe we want to keep this?~~ Update: yes, we need this - [x] parsing + expansion for test suite syntax, if applicable
- [x] proper handling of where clauses
- [x] docs
- [X] UI tests
Related Issues
- closes #10848