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//!
72//! If this block of code is to be benchmarked, then the benchmarking code must be written such that
73//! it captures the worst case.
74//!
75//! ## Gluing Pallet Benchmarking with Runtime
76//!
77//! FRAME pallets are mandated to provide their own benchmarking code. Runtimes contain the
78//! boilerplate needed to run these benchmarking (see [Running Benchmarks
79//! below](#running-benchmarks)). The outcome of running these benchmarks are meant to be fed back
80//! into the pallet via a conventional `trait WeightInfo` on `Config`:
81#![doc = docify::embed!("src/reference_docs/frame_benchmarking_weight.rs", WeightInfo)]
82//!
83//! Then, individual functions of this trait are the final values that we assigned to the
84//! [`frame::pallet_macros::weight`] attribute:
85#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer_2)]
86//!
87//! ## Manual Refund
88//!
89//! Back to the assumption of writing benchmarks for worst case: Sometimes, the pre-dispatch weight
90//! significantly differ from the post-dispatch actual weight consumed. This can be expressed with
91//! the following FRAME syntax:
92#![doc = docify::embed!("./src/reference_docs/frame_benchmarking_weight.rs", simple_transfer_3)]
93//!
94//! ## Running Benchmarks
95//!
96//! Two ways exist to run the benchmarks of a runtime.
97//!
98//! 1. The old school way: Most Polkadot-SDK based nodes (such as the ones integrated in
99//!    [`templates`]) have a `benchmark` subcommand integrated into themselves.
100//! 2. The more [`crate::reference_docs::omni_node`] compatible way of running the benchmarks would
101//!    be using [`frame-omni-bencher`] CLI, which only relies on a runtime.
102//!
103//! Note that by convention, the runtime and pallets always have their benchmarking code feature
104//! gated as behind `runtime-benchmarks`. So, the runtime should be compiled with `--features
105//! runtime-benchmarks`.
106//!
107//! ## Automatic Refund of `proof_size`.
108//!
109//! A new feature in FRAME allows the runtime to be configured for "automatic refund" of the proof
110//! size weight. This is very useful for maximizing the throughput of parachains. Please see:
111//! [`crate::guides::enable_pov_reclaim`].
112//!
113//! ## Summary
114//!
115//! Polkadot-SDK runtimes use a more performant VM, namely WASM, which does not have metering. In
116//! return they have to be benchmarked to provide an upper bound on the resources they consume. This
117//! upper bound is represented as [`sp_weights::Weight`].
118//!
119//! ## Future: PolkaVM
120//!
121//! With the transition of Polkadot relay chain to [JAM], a set of new features are being
122//! introduced, one of which being a new virtual machine named [PolkaVM] that is as flexible as
123//! WASM, but also capable of metering. This might alter the future of benchmarking in FRAME and
124//! Polkadot-SDK, rendering them not needed anymore once PolkaVM is fully integrated into
125//! Polkadot-sdk. For a basic explanation of JAM and PolkaVM, see [here](https://blog.kianenigma.com/posts/tech/demystifying-jam/#pvm).
126//!
127//!
128//! [`frame-omni-bencher`]: https://crates.io/crates/frame-omni-bencher
129//! [`templates`]: crate::polkadot_sdk::templates
130//! [PolkaVM]: https://github.com/koute/polkavm
131//! [JAM]: https://graypaper.com
132
133#[frame::pallet(dev_mode)]
134#[allow(unused_variables, unreachable_code, unused, clippy::diverging_sub_expression)]
135pub mod pallet {
136	use frame::prelude::*;
137
138	#[docify::export]
139	pub trait WeightInfo {
140		fn simple_transfer() -> Weight;
141	}
142
143	#[pallet::config]
144	pub trait Config: frame_system::Config {
145		type WeightInfo: WeightInfo;
146	}
147
148	#[pallet::pallet]
149	pub struct Pallet<T>(_);
150
151	#[pallet::call]
152	impl<T: Config> Pallet<T> {
153		#[docify::export]
154		#[pallet::weight(10_000)]
155		pub fn simple_transfer(
156			origin: OriginFor<T>,
157			destination: T::AccountId,
158			amount: u32,
159		) -> DispatchResult {
160			let destination_exists = todo!();
161			if destination_exists {
162				// simpler code path
163			} else {
164				// more complex code path
165			}
166			Ok(())
167		}
168
169		#[docify::export]
170		#[pallet::weight(T::WeightInfo::simple_transfer())]
171		pub fn simple_transfer_2(
172			origin: OriginFor<T>,
173			destination: T::AccountId,
174			amount: u32,
175		) -> DispatchResult {
176			let destination_exists = todo!();
177			if destination_exists {
178				// simpler code path
179			} else {
180				// more complex code path
181			}
182			Ok(())
183		}
184
185		#[docify::export]
186		// This is the worst-case, pre-dispatch weight.
187		#[pallet::weight(T::WeightInfo::simple_transfer())]
188		pub fn simple_transfer_3(
189			origin: OriginFor<T>,
190			destination: T::AccountId,
191			amount: u32,
192		) -> DispatchResultWithPostInfo {
193			// ^^ Notice the new return type
194			let destination_exists = todo!();
195			if destination_exists {
196				// simpler code path
197				// Note that need for .into(), to convert `()` to `PostDispatchInfo`
198				// See: https://paritytech.github.io/polkadot-sdk/master/frame_support/dispatch/struct.PostDispatchInfo.html#impl-From%3C()%3E-for-PostDispatchInfo
199				Ok(().into())
200			} else {
201				// more complex code path
202				let actual_weight =
203					todo!("this can likely come from another benchmark that is NOT the worst case");
204				let pays_fee = todo!("You can set this to `Pays::Yes` or `Pays::No` to change if this transaction should pay fees");
205				Ok(frame::deps::frame_support::dispatch::PostDispatchInfo {
206					actual_weight: Some(actual_weight),
207					pays_fee,
208				})
209			}
210		}
211	}
212}