referrerpolicy=no-referrer-when-downgrade

polkadot_sdk_docs/reference_docs/
frame_benchmarking_weight.rs

1//! # FRAME Benchmarking and Weights.
2//!
3//! This reference doc explores the concept of weights within Polkadot-SDK runtimes, and more
4//! specifically how FRAME-based runtimes handle it.
5//!
6//! ## Metering
7//!
8//! The existence of "weight" as a concept in Polkadot-SDK is a direct consequence of the usage of
9//! WASM as a virtual machine. Unlike a metered virtual machine like EVM, where every instruction
10//! can have a (fairly) deterministic "cost" (also known as "gas price") associated with it, WASM is
11//! a stack machine with more complex instruction set, and more unpredictable execution times. This
12//! means that unlike EVM, it is not possible to implement a "metering" system in WASM. A metering
13//! system is one in which instructions are executed one by one, and the cost/gas is stored in an
14//! accumulator. The execution may then halt once a gas limit is reached.
15//!
16//! In Polkadot-SDK, the WASM runtime is not assumed to be metered.
17//!
18//! ## Trusted Code
19//!
20//! Another important difference is that EVM is mostly used to express smart contracts, which are
21//! foreign and untrusted codes from the perspective of the blockchain executing them. In such
22//! cases, metering is crucial, in order to ensure a malicious code cannot consume more gas than
23//! expected.
24//!
25//! This assumption does not hold about the runtime of Polkadot-SDK-based blockchains. The runtime
26//! is trusted code, and it is assumed to be written by the same team/developers who are running the
27//! blockchain itself. Therefore, this assumption of "untrusted foreign code" does not hold.
28//!
29//! This is why the runtime can opt for a more performant, more flexible virtual machine like WASM,
30//! and get away without having metering.
31//!
32//! ## Benchmarking
33//!
34//! With the matter of untrusted code execution out of the way, the need for strict metering goes
35//! out of the way. Yet, it would still be very beneficial for block producers to be able to know an
36//! upper bound on how much resources a operation is going to consume before actually executing that
37//! operation. This is why FRAME has a toolkit for benchmarking pallets: So that this upper bound
38//! can be empirically determined.
39//!
40//! > Note: Benchmarking is a static analysis: It is all about knowing the upper bound of how much
41//! > resources an operation takes statically, without actually executing it. In the context of
42//! > FRAME extrinsics, this static-ness is expressed by the keyword "pre-dispatch".
43//!
44//! To understand why this upper bound is needed, consider the following: A block producer knows
45//! they have 20ms left to finish producing their block, and wishes to include more transactions in
46//! the block. Yet, in a metered environment, it would not know which transaction is likely to fit
47//! the 20ms. In a benchmarked environment, it can examine the transactions for their upper bound,
48//! and include the ones that are known to fit based on the worst case.
49//!
50//! The benchmarking code can be written as a part of FRAME pallet, using the macros provided in
51//! [`frame_benchmarking`]. See any of the existing pallets in `polkadot-sdk`, or the pallets in our
52//! [`crate::polkadot_sdk::templates`] for examples.
53//!
54//! ## Weight
55//!
56//! Finally, [`sp_weights::Weight`] is the output of the benchmarking process. It is a
57//! two-dimensional data structure that demonstrates the resources consumed by a given block of
58//! code (for example, a transaction). The two dimensions are:
59//!
60//! * reference time: The time consumed in pico-seconds, on a reference hardware.
61//! * proof size: The amount of storage proof necessary to re-execute the block of code. This is
62//!   mainly needed for parachain <> relay-chain verification.
63//!
64//! ## How To Write Benchmarks: Worst Case
65//!
66//! The most important detail about writing benchmarking code is that it must be written such that
67//! it captures the worst case execution of any block of code.
68//!
69//! Consider:
70#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer)]
71//! If this block of code is to be benchmarked, then the benchmarking code must be written such that
72//! it captures the worst case.
73//!
74//! ## Gluing Pallet Benchmarking with Runtime
75//!
76//! FRAME pallets are mandated to provide their own benchmarking code. Runtimes contain the
77//! boilerplate needed to run these benchmarking (see [Running Benchmarks
78//! below](#running-benchmarks)). The outcome of running these benchmarks are meant to be fed back
79//! into the pallet via a conventional `trait WeightInfo` on `Config`:
80#![doc = docify::embed!("src/reference_docs/frame_benchmarking_weight.rs", WeightInfo)]
81//! Then, individual functions of this trait are the final values that we assigned to the
82//! [`frame::pallet_macros::weight`] attribute:
83#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer_2)]
84//! ## Manual Refund
85//!
86//! Back to the assumption of writing benchmarks for worst case: Sometimes, the pre-dispatch weight
87//! significantly differ from the post-dispatch actual weight consumed. This can be expressed with
88//! the following FRAME syntax:
89#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer_3)]
90//! ## Running Benchmarks
91//!
92//! Two ways exist to run the benchmarks of a runtime.
93//!
94//! 1. The old school way: Most Polkadot-SDK based nodes (such as the ones integrated in
95//!    [`templates`]) have a `benchmark` subcommand integrated into themselves.
96//! 2. The more [`crate::reference_docs::omni_node`] compatible way of running the benchmarks would
97//!    be using [`frame-omni-bencher`] CLI, which only relies on a runtime.
98//!
99//! Note that by convention, the runtime and pallets always have their benchmarking code feature
100//! gated as behind `runtime-benchmarks`. So, the runtime should be compiled with `--features
101//! runtime-benchmarks`.
102//!
103//! ## Automatic Refund of `proof_size`.
104//!
105//! A new feature in FRAME allows the runtime to be configured for "automatic refund" of the proof
106//! size weight. This is very useful for maximizing the throughput of parachains. Please see:
107//! [`crate::guides::enable_pov_reclaim`].
108//!
109//! ## Summary
110//!
111//! Polkadot-SDK runtimes use a more performant VM, namely WASM, which does not have metering. In
112//! return they have to be benchmarked to provide an upper bound on the resources they consume. This
113//! upper bound is represented as [`sp_weights::Weight`].
114//!
115//! ## Future: PolkaVM
116//!
117//! With the transition of Polkadot relay chain to [JAM], a set of new features are being
118//! introduced, one of which being a new virtual machine named [PolkaVM] that is as flexible as
119//! WASM, but also capable of metering. This might alter the future of benchmarking in FRAME and
120//! Polkadot-SDK, rendering them not needed anymore once PolkaVM is fully integrated into
121//! Polkadot-sdk. For a basic explanation of JAM and PolkaVM, see [here](https://blog.kianenigma.com/posts/tech/demystifying-jam/#pvm).
122//!
123//!
124//! [`frame-omni-bencher`]: https://crates.io/crates/frame-omni-bencher
125//! [`templates`]: crate::polkadot_sdk::templates
126//! [PolkaVM]: https://github.com/koute/polkavm
127//! [JAM]: https://graypaper.com
128
129#[frame::pallet(dev_mode)]
130#[allow(unused_variables, unreachable_code, unused, clippy::diverging_sub_expression)]
131pub mod pallet {
132	use frame::prelude::*;
133
134	#[docify::export]
135	pub trait WeightInfo {
136		fn simple_transfer() -> Weight;
137	}
138
139	#[pallet::config]
140	pub trait Config: frame_system::Config {
141		type WeightInfo: WeightInfo;
142	}
143
144	#[pallet::pallet]
145	pub struct Pallet<T>(_);
146
147	#[pallet::call]
148	impl<T: Config> Pallet<T> {
149		#[docify::export]
150		#[pallet::weight(10_000)]
151		pub fn simple_transfer(
152			origin: OriginFor<T>,
153			destination: T::AccountId,
154			amount: u32,
155		) -> DispatchResult {
156			let destination_exists = todo!();
157			if destination_exists {
158				// simpler code path
159			} else {
160				// more complex code path
161			}
162			Ok(())
163		}
164
165		#[docify::export]
166		#[pallet::weight(T::WeightInfo::simple_transfer())]
167		pub fn simple_transfer_2(
168			origin: OriginFor<T>,
169			destination: T::AccountId,
170			amount: u32,
171		) -> DispatchResult {
172			let destination_exists = todo!();
173			if destination_exists {
174				// simpler code path
175			} else {
176				// more complex code path
177			}
178			Ok(())
179		}
180
181		#[docify::export]
182		// This is the worst-case, pre-dispatch weight.
183		#[pallet::weight(T::WeightInfo::simple_transfer())]
184		pub fn simple_transfer_3(
185			origin: OriginFor<T>,
186			destination: T::AccountId,
187			amount: u32,
188		) -> DispatchResultWithPostInfo {
189			// ^^ Notice the new return type
190			let destination_exists = todo!();
191			if destination_exists {
192				// simpler code path
193				// Note that need for .into(), to convert `()` to `PostDispatchInfo`
194				// See: https://paritytech.github.io/polkadot-sdk/master/frame_support/dispatch/struct.PostDispatchInfo.html#impl-From%3C()%3E-for-PostDispatchInfo
195				Ok(().into())
196			} else {
197				// more complex code path
198				let actual_weight =
199					todo!("this can likely come from another benchmark that is NOT the worst case");
200				let pays_fee = todo!("You can set this to `Pays::Yes` or `Pays::No` to change if this transaction should pay fees");
201				Ok(frame::deps::frame_support::dispatch::PostDispatchInfo {
202					actual_weight: Some(actual_weight),
203					pays_fee,
204				})
205			}
206		}
207	}
208}