Pallet Benchmarking
Introduction¶
After validating your pallet through testing and integrating it into your runtime, the next crucial step is benchmarking. Testing procedures were detailed in the Pallet Unit Testing tutorial, while runtime integration was covered in the Add Pallets to the Runtime guide.
Benchmarking assigns precise weight to each extrinsic, measuring their computational and storage costs. These derived weights enable accurate fee calculation and resource allocation within the runtime.
This tutorial demonstrates how to:
- Configure your development environment for benchmarking
- Create and implement benchmark tests for your extrinsics
- Apply benchmark results to your pallet's extrinsics
For comprehensive information about benchmarking concepts, refer to the Benchmarking guide.
Environment Setup¶
Follow these steps to prepare your environment for pallet benchmarking:
-
Install the
frame-omni-bencher
command-line tool: -
Update your pallet's
Cargo.toml
file in thepallets/custom-pallet
directory with the following modifications:-
Add the
frame-benchmarking
dependency: -
Enable benchmarking in the
std
features: -
Add the
runtime-benchmarks
feature flag:
-
-
Add your pallet to the runtime's benchmark configuration:
-
Register your pallet in
runtime/src/benchmarks.rs
:benchmarks.rsframe_benchmarking::define_benchmarks!( [frame_system, SystemBench::<Runtime>] [pallet_balances, Balances] [pallet_session, SessionBench::<Runtime>] [pallet_timestamp, Timestamp] [pallet_message_queue, MessageQueue] [pallet_sudo, Sudo] [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] [custom_pallet, CustomPallet] );
-
Enable runtime benchmarking for your pallet in
runtime/Cargo.toml
:
-
-
Set up the benchmarking module in your pallet:
-
Create a new
benchmarking.rs
file in your pallet directory: -
Add the benchmarking module to your pallet. In the pallet
lib.rs
file add the following:
The
benchmarking
module is gated behind theruntime-benchmarks
feature flag. It will only be compiled when this flag is explicitly enabled in your project'sCargo.toml
or via the--features runtime-benchmarks
compilation flag. -
Implement Benchmark Tests¶
When writing benchmarking tests for your pallet, you'll create specialized test functions for each extrinsic, similar to unit tests. These tests use the mock runtime you created earlier for testing, allowing you to leverage its utility functions.
Every benchmark test must follow a three-step pattern:
- Setup - perform any necessary setup before calling the extrinsic. This might include creating accounts, setting initial states, or preparing test data
- Execute the extrinsic - execute the actual extrinsic using the
#[extrinsic_call]
macro. This must be a single line that calls your extrinsic function with the origin as its first argument - Verification - check that the extrinsic worked correctly within the benchmark context by checking the expected state changes
Check the following example on how to benchmark the increment
extrinsic:
#[benchmark]
fn increment() {
let caller: T::AccountId = whitelisted_caller();
assert_ok!(CustomPallet::<T>::set_counter_value(
RawOrigin::Root.into(),
5u32
));
#[extrinsic_call]
increment(RawOrigin::Signed(caller.clone()), 1);
assert_eq!(CounterValue::<T>::get(), Some(6u32.into()));
assert_eq!(UserInteractions::<T>::get(caller), 1u32.into());
}
This benchmark test:
- Creates a whitelisted caller and sets an initial counter value of 5
- Calls the increment extrinsic to increase the counter by 1
- Verifies that the counter was properly incremented to 6 and that the user's interaction was recorded in storage
This example demonstrates how to properly set up state, execute an extrinsic, and verify its effects during benchmarking.
Now, implement the complete set of benchmark tests. Copy the following content in the benchmarking.rs
file:
#![cfg(feature = "runtime-benchmarks")]
use super::{Pallet as CustomPallet, *};
use frame_benchmarking::v2::*;
use frame_support::assert_ok;
#[benchmarks]
mod benchmarks {
use super::*;
#[cfg(test)]
use crate::pallet::Pallet as CustomPallet;
use frame_system::RawOrigin;
#[benchmark]
fn set_counter_value() {
#[extrinsic_call]
set_counter_value(RawOrigin::Root, 5);
assert_eq!(CounterValue::<T>::get(), Some(5u32.into()));
}
#[benchmark]
fn increment() {
let caller: T::AccountId = whitelisted_caller();
assert_ok!(CustomPallet::<T>::set_counter_value(
RawOrigin::Root.into(),
5u32
));
#[extrinsic_call]
increment(RawOrigin::Signed(caller.clone()), 1);
assert_eq!(CounterValue::<T>::get(), Some(6u32.into()));
assert_eq!(UserInteractions::<T>::get(caller), 1u32.into());
}
#[benchmark]
fn decrement() {
let caller: T::AccountId = whitelisted_caller();
assert_ok!(CustomPallet::<T>::set_counter_value(
RawOrigin::Root.into(),
5u32
));
#[extrinsic_call]
decrement(RawOrigin::Signed(caller.clone()), 1);
assert_eq!(CounterValue::<T>::get(), Some(4u32.into()));
assert_eq!(UserInteractions::<T>::get(caller), 1u32.into());
}
impl_benchmark_test_suite!(CustomPallet, crate::mock::new_test_ext(), crate::mock::Test);
}
The #[benchmark]
macro marks these functions as benchmark tests, while the #[extrinsic_call]
macro specifically identifies which line contains the extrinsic being measured. For more information, see the frame_benchmarking Rust docs.
Execute the Benchmarking¶
After implementing your benchmark test suite, you'll need to execute the tests and generate the weights for your extrinsics. This process involves building your runtime with benchmarking features enabled and using the frame-omni-bencher
CLI tool. To do that, follow these steps:
-
Build your runtime with the
runtime-benchmarks
feature enabled:This special build includes all the necessary benchmarking code that's normally excluded from production builds.
-
Create a
weights.rs
file in your pallet'ssrc/
directory. This file will store the auto-generated weight calculations: -
Before running the benchmarking tool, you'll need a template file that defines how weight information should be formatted. Download the official template from the Polkadot SDK repository and save it in your project folders for future use:
-
Execute the benchmarking process using the
frame-omni-bencher
CLI:frame-omni-bencher v1 benchmark pallet \ --runtime target/release/wbuild/parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm \ --pallet "custom_pallet" \ --extrinsic "" \ --template ./pallets/benchmarking/frame-weight-template.hbs \ --output ./pallets/custom-pallet/src/weights.rs
When the benchmarking process completes, your weights.rs
file will contain auto-generated code with weight calculations for each of your pallet's extrinsics. These weights help ensure fair and accurate fee calculations when your pallet is used in a production environment.
Add Benchmarking Weights to the Pallet¶
After generating the weight calculations, you need to integrate these weights into your pallet's code. This integration ensures your pallet properly accounts for computational costs in its extrinsics.
First, add the necessary module imports to your pallet. These imports make the weights available to your code:
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
use crate::weights::WeightInfo;
Next, update your pallet's Config
trait to include weight information. Define the WeightInfo
type:
// Configuration trait for the pallet.
#[pallet::config]
pub trait Config: frame_system::Config {
// Defines the event type for the pallet.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
// Defines the maximum value the counter can hold.
#[pallet::constant]
type CounterMaxValue: Get<u32>;
/// A type representing the weights required by the dispatchables of this pallet.
type WeightInfo: WeightInfo;
}
Now you can assign weights to your extrinsics. Here's how to add weight calculations to the set_counter_value
function:
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::set_counter_value())]
pub fn set_counter_value(origin: OriginFor<T>, new_value: u32) -> DispatchResult {
ensure_root(origin)?;
ensure!(
new_value <= T::CounterMaxValue::get(),
Error::<T>::CounterValueExceedsMax
);
CounterValue::<T>::put(new_value);
Self::deposit_event(Event::<T>::CounterValueSet {
counter_value: new_value,
});
Ok(())
}
You must apply similar weight annotations to the other extrinsics in your pallet. Add the #[pallet::weight(T::WeightInfo::function_name())]
attribute to both increment
and decrement
, replacing function_name
with the respective function names from your WeightInfo
trait.
For testing purposes, you must implement the weight calculations in your mock runtime. Open custom-pallet/src/mock.rs
and add:
impl custom_pallet::Config for Test {
type RuntimeEvent = RuntimeEvent;
type CounterMaxValue = CounterMaxValue;
type WeightInfo = custom_pallet::weights::SubstrateWeight<Test>;
}
Finally, configure the actual weight values in your production runtime. In runtime/src/config/mod.rs
, add:
// Configure custom pallet.
impl custom_pallet::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type CounterMaxValue = CounterMaxValue;
type WeightInfo = custom_pallet::weights::SubstrateWeight<Runtime>;
}
Your pallet is now complete with full testing and benchmarking support, ready for production use.
Where to Go Next¶
-
Tutorial Deploy on Paseo TestNet
Deploy your Polkadot SDK blockchain on Paseo! Follow this step-by-step guide for a seamless journey to a successful TestNet deployment.
| Created: January 9, 2025