Internals
Benchmarks and weights
How extrinsic weights are benchmarked, generated, and validated in CI.
Every extrinsic in the runtime has a weight — a measure of the computational resources it consumes. Weights are used to calculate transaction fees and to prevent blocks from being overloaded.
Weights are defined in weights.rs files inside each pallet and are generated
by running benchmarks on reference hardware.
Tools
| Tool | Purpose |
|---|---|
scripts/benchmark_all.sh | Generate weights.rs for one or all pallets (runs real benchmarks) |
weight-compare | Compare two weights.rs files and report drift (used by CI) |
weight-compare lives in support/weight-tools/ and has no heavy dependencies
(no runtime build required).
Adding a new pallet
-
Write your benchmarks in
pallets/<name>/src/benchmarking.rsusing#[benchmarks]and#[benchmark]macros. -
Create
pallets/<name>/src/weights.rsmanually. Copy the structure from any existing pallet (e.g.pallets/drand/src/weights.rs) and replace the function signatures with yours, usingWeight::from_parts(0, 0)as the body so the pallet compiles immediately:pub trait WeightInfo { fn my_extrinsic() -> Weight; } pub struct SubstrateWeight<T>(PhantomData<T>); impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> { fn my_extrinsic() -> Weight { Weight::from_parts(0, 0) } } impl WeightInfo for () { fn my_extrinsic() -> Weight { Weight::from_parts(0, 0) } } -
Add
pub mod weights;to your pallet'slib.rs. -
Add
type WeightInfo: crate::weights::WeightInfo;to your pallet'sConfigtrait. -
Use
T::WeightInfo::extrinsic_name()in#[pallet::weight(...)]annotations instead of hardcodedWeight::from_parts(...). -
Wire up in
runtime/src/lib.rs:type WeightInfo = pallet_<name>::weights::SubstrateWeight<Runtime>; -
Add
type WeightInfo = ();to all test mocks implementing your pallet'sConfig. -
Register the pallet in the
define_benchmarks!macro inruntime/src/lib.rsso the benchmark runner can discover it:define_benchmarks!( // ...existing pallets... [pallet_<name>, PalletInstance] );
The benchmark scripts auto-discover pallets by scanning for directories under
pallets/ that have src/weights.rs plus src/benchmarking.rs (or
src/benchmarks.rs), and that are registered in define_benchmarks!. No
manual registration in scripts is needed. Careful with step 8
(define_benchmarks!): an unregistered pallet is filtered out by discovery
and silently skipped in all-pallets mode and in CI; only the single-pallet
invocation (./scripts/benchmark_all.sh pallet_<name>) fails, with an
"unknown pallet" error.
CI will generate real weights automatically when the PR is opened.
Adding a new extrinsic to an existing pallet
-
Write the benchmark in
benchmarking.rs. -
Add the function signature to the
WeightInfotrait inweights.rs, and aWeight::from_parts(0, 0)body to both theSubstrateWeight<T>and()impls so the pallet continues to compile:// in trait WeightInfo: fn new_extrinsic() -> Weight; // in both impls: fn new_extrinsic() -> Weight { Weight::from_parts(0, 0) } -
Add
#[pallet::weight(T::WeightInfo::new_extrinsic())]to the extrinsic.
CI will generate real weights automatically when the PR is opened.
Parameterized weights
For extrinsics whose cost scales with an input, use Linear<min, max>
parameters in the benchmark. You can use one or more parameters:
// Single parameter
#[benchmark]
fn refund(k: Linear<1, 100>) {
// setup with k contributors...
#[extrinsic_call]
_(origin, crowdloan_id);
}
// Multiple parameters
#[benchmark]
fn transfer_batch(n: Linear<1, 256>, m: Linear<1, 64>) {
// setup with n recipients and m tokens each...
#[extrinsic_call]
_(origin, recipients, amounts);
}This generates weight functions with matching signatures:
fn refund(k: u32) -> Weight;
fn transfer_batch(n: u32, m: u32) -> Weight;The generated weight includes base values plus per-parameter slope terms
(e.g., base + slope_k * k for single parameter, or
base + slope_n * n + slope_m * m for multiple). Reference them as:
#[pallet::weight(T::WeightInfo::refund(T::MaxContributors::get()))]
#[pallet::weight(T::WeightInfo::transfer_batch(recipients.len() as u32, max_tokens))]CI workflow
The Validate-Benchmarks workflow (.github/workflows/run-benchmarks.yml)
runs on every PR:
- Builds the node with
--features runtime-benchmarks - Runs benchmarks for every pallet, generating new
weights.rsto temp files - Uses
weight-compareto compare old vs new values with a 40% threshold- Base weight: threshold-based (allows measurement noise)
- Reads/writes: exact match (these are deterministic)
- Component slopes: threshold-based for weights, exact for reads/writes
- If drift is detected, prepares a patch in
.bench_patch/ - Adding the
apply-benchmark-patchlabel auto-applies the patch
To skip benchmarks on a PR, add the skip-validate-benchmarks label. This can
be added at any point during the job — it's checked between expensive steps.
Running benchmarks locally
# Build + generate weights for all pallets
./scripts/benchmark_all.sh
# Build + generate weights for a single pallet
./scripts/benchmark_all.sh pallet_subtensor
# Run benchmark unit tests (fast, no real measurements — just checks setup)
cargo test -p pallet-subtensor --features runtime-benchmarks benchmarks
# Compare two weight files
cargo run -p subtensor-weight-tools --bin weight-compare -- \
--old pallets/foo/src/weights.rs \
--new /tmp/new_weights.rs \
--threshold 40Weight file structure
Generated weights.rs files contain:
WeightInfotrait — one function per benchmarked extrinsicSubstrateWeight<T>impl — used in the runtime, referencesT::DbWeight()impl — used in tests, referencesRocksDbWeight
The () fallback uses RocksDbWeight constants directly. The production
runtime uses T::DbWeight::get(), which runtime/src/lib.rs also configures
as type DbWeight = RocksDbWeight (matching the node's default RocksDb
backend), so in this repo both impls resolve to the same constants.
The files are generated by the frame-benchmarking-cli using the Handlebars
template at .maintain/frame-weight-template.hbs.