Add benchmarks

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.

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.`,

Steps

1. Set-up benchmarking for your pallet

  1. In the pallet's Cargo.toml, add the frame-benchmarking crate (with appropriate tag and version) and the runtime-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",
    ]
  2. 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
    }
  3. 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. Here b is a variable input that is passed into the extrinsic set_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.

  4. 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.

  1. Update your runtime's Cargo.toml file to include the runtime-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'
    ]
  2. 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 the runtime or Import a pallet.
  3. 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

  1. Build your project with the benchmarks enabled:

    cargo build --release --features runtime-benchmarks
  2. 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 pallet \
    --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 Use custom weights from benchmarking to learn how to configure your pallet to use those weights.

Examples