Add benchmarking to your pallet
Goal
Add FRAME's benchmarking tool to your pallet and write a simple benchmark.
Use Cases
Setting up your pallet to be able to benchmark your extrinsics.
Overview
This guide steps through the process of adding benchmarking to a pallet and runtime. In addition, it covers the steps of writing a simple benchmark for a pallet as well as testing and running the benchmarking tool. This guide does not cover updating weights with benchmarked values.
Steps
1. Set-up benchmarking for your pallet
In the pallet's
Cargo.toml
, add theframe-benchmarking
crate (with appropriate tag and version) and theruntime-benchmarks
feature.pallets/example/Cargo.toml
frame-benchmarking = { default-features = false, git = "https://github.com/paritytech/substrate.git", optional = true, branch = "<polkadot-vM.m.p>" } [features] # -- snip -- runtime-benchmarks = ["frame-benchmarking"] std = [ # -- snip -- "frame-benchmarking/std", ]
Create a new Rust module for your benchmarks in your pallet's folder (an example of
/pallets/template/src/benchmarking.rs
), and create the basic structure:pallets/example/src/benchmarking.rs
//! Benchmarks for Template Pallet #![cfg(feature = "runtime-benchmarks")] use crate::*; use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; use frame_system::RawOrigin; benchmarks!{ // Individual benchmarks are placed here }
Each benchmark case has up to three sections: a setup section, an execution section, and optionally a verification section at the end.
benchmarks!{ benchmark_name { /* setup initial state */ }: { /* the code to be benchmarked */ } verify { /* verifying final state */ } }
We'll refer to an extremely basic example of a benchmark from the Example Pallet. Take a look at the extrinsic we'll be benchmarking for:
// This will measure the execution time of `set_dummy` for b in [1..1000] range. set_dummy { let b in 1 .. 1000; }: set_dummy(RawOrigin::Root, b.into());
The name of the benchmark is
set_dummy
. Hereb
is a variable input that is passed into the extrinsicset_dummy
which may affect the extrinsic execution time.b
will be varied between 1 to 1,000, where we will repeat and measure the benchmark at the different values.In this example, the extrinsic
set_dummy
is called in the execution section, and we do not verify the result at the end.Once you have written your benchmarks, you should make sure they execute properly by testing them. Add this macro at the bottom of your benchmarking module:
pallets/example/src/benchmarking.rs
impl_benchmark_test_suite!( YourPallet, crate::mock::new_test_ext(), crate::mock::Test, );
The
impl_benchmark_test_suite!
macro takes three inputs: the Pallet struct generated by your pallet, a function that generates a test genesis storage (i.e.new_text_ext()
), and a full runtime struct. These things you should get from your normal pallet unit tests.We will use your test environment and execute the benchmarks similar to how they would execute when actually running the benchmarks. If all tests pass, then it's likely that things will work when you actually run your benchmarks!
2. Add benchmarking to your runtime
With all the code completed on the pallet side, you need to also enable your full runtime to allow benchmarking.
Update your runtime's
Cargo.toml
file to include theruntime-benchmarking
features:pallet-you-created = { default-features = false, path = "../pallets/pallet-you-created"} [features] runtime-benchmarks = [ # -- snip -- 'pallet-you-created/runtime-benchmarks' ] std = [ # -- snip -- 'pallet-you-created/std' ]
Add your new pallet to your runtime just as you would any other pallet. If you need more details check out the Add a Pallet to Your Runtime Tutorial or this guide on integrating a pallet to your runtime.
Then, in addition to your normal runtime configuration, you also need to update the benchmarking section of your runtime. To add our new benchmarks, we simply add a new line with the
add_benchmark!
macro:#[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark<Block> for Runtime { fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig ) -> Result<( Vec<frame_benchmarking::BenchmarkBatch>, Vec<StorageInfo>), sp_runtime::RuntimeString, > { // -- snip -- let whitelist: Vec<TrackedStorageKey> = vec![ // You can whitelist any storage keys you do not want to track here ]; let storage_info = AllPalletsWithSystem::storage_info(); let mut batches = Vec::<BenchmarkBatch>::new(); let params = (&config, &whitelist); // Adding the pallet for which you will perform the benchmarking add_benchmark!(params, batches, pallet_you_crated, YourPallet); // -- snip -- if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches, storage_info) } }
3. Run your benchmarks
Build your project with the benchmarks enabled:
cargo build --release --features runtime-benchmarks
Once this is done, you should be able to run the
benchmark
subcommand from your project's top level directory to view all CLI options. This will also ensure that benchmarking has been properly integrated:./target/release/node-template benchmark --help
The Benchmarking CLI has a lot of options which can help you automate your benchmarking.
Execute the following command to run standard benchmarking for your pallet_you_created
:
./target/release/node-template benchmark \
--chain dev \
--execution wasm \
--wasm-execution compiled \
--pallet pallet_you_crated \
--extrinsic '\*' \
--steps 20 \
--repeat 10 \
--json-file=raw.json \
--output ./pallets/src/pallet-created/weights.rs
This will create a weights.rs
file inside your pallet's directory.
Refer to this guide to learn how to configure your pallet to use those weights.