referrerpolicy=no-referrer-when-downgrade

frame_benchmarking_pallet_pov/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! All benchmarks in this file are just for debugging the PoV calculation logic, they are unused.
19
20#![cfg(feature = "runtime-benchmarks")]
21
22use super::*;
23
24use frame_benchmarking::v2::*;
25use frame_support::traits::UnfilteredDispatchable;
26use frame_system::{Pallet as System, RawOrigin};
27use sp_runtime::traits::Hash;
28
29#[benchmarks]
30mod benchmarks {
31	use super::*;
32
33	#[benchmark]
34	fn storage_single_value_read() {
35		Value::<T>::put(123);
36
37		#[block]
38		{
39			assert_eq!(Value::<T>::get(), Some(123));
40		}
41	}
42
43	#[benchmark(pov_mode = Ignored)]
44	fn storage_single_value_ignored_read() {
45		Value::<T>::put(123);
46		#[block]
47		{
48			assert_eq!(Value::<T>::get(), Some(123));
49		}
50	}
51
52	#[benchmark(pov_mode = MaxEncodedLen {
53		Pov::Value2: Ignored
54	})]
55	fn storage_single_value_ignored_some_read() {
56		Value::<T>::put(123);
57		Value2::<T>::put(123);
58
59		#[block]
60		{
61			assert_eq!(Value::<T>::get(), Some(123));
62			assert_eq!(Value2::<T>::get(), Some(123));
63		}
64	}
65
66	#[benchmark]
67	fn storage_single_value_read_twice() {
68		Value::<T>::put(123);
69
70		#[block]
71		{
72			assert_eq!(Value::<T>::get(), Some(123));
73			assert_eq!(Value::<T>::get(), Some(123));
74		}
75	}
76
77	#[benchmark]
78	fn storage_single_value_write() {
79		#[block]
80		{
81			Value::<T>::put(123);
82		}
83
84		assert_eq!(Value::<T>::get(), Some(123));
85	}
86
87	#[benchmark]
88	fn storage_single_value_kill() {
89		Value::<T>::put(123);
90
91		#[block]
92		{
93			Value::<T>::kill();
94		}
95
96		assert!(!Value::<T>::exists());
97	}
98
99	// This benchmark and the following are testing a storage map with adjacent storage items.
100	//
101	// First a storage map is filled and a specific number of other storage items is
102	// created. Then the one value is read from the map. This demonstrates that the number of other
103	// nodes in the Trie influences the proof size. The number of inserted nodes can be interpreted
104	// as the number of `StorageMap`/`StorageValue` in the whole runtime.
105	#[benchmark(pov_mode = Measured)]
106	fn storage_1m_map_read_one_value_two_additional_layers() {
107		(0..(1 << 10)).for_each(|i| Map1M::<T>::insert(i, i));
108		// Assume there are 16-256 other storage items.
109		(0..(1u32 << 4)).for_each(|i| {
110			let k = T::Hashing::hash(&i.to_be_bytes());
111			frame_support::storage::unhashed::put(k.as_ref(), &i);
112		});
113
114		#[block]
115		{
116			assert_eq!(Map1M::<T>::get(1 << 9), Some(1 << 9));
117		}
118	}
119
120	#[benchmark(pov_mode = Measured)]
121	fn storage_1m_map_read_one_value_three_additional_layers() {
122		(0..(1 << 10)).for_each(|i| Map1M::<T>::insert(i, i));
123		// Assume there are 256-4096 other storage items.
124		(0..(1u32 << 8)).for_each(|i| {
125			let k = T::Hashing::hash(&i.to_be_bytes());
126			frame_support::storage::unhashed::put(k.as_ref(), &i);
127		});
128
129		#[block]
130		{
131			assert_eq!(Map1M::<T>::get(1 << 9), Some(1 << 9));
132		}
133	}
134
135	#[benchmark(pov_mode = Measured)]
136	fn storage_1m_map_read_one_value_four_additional_layers() {
137		(0..(1 << 10)).for_each(|i| Map1M::<T>::insert(i, i));
138		// Assume there are 4096-65536 other storage items.
139		(0..(1u32 << 12)).for_each(|i| {
140			let k = T::Hashing::hash(&i.to_be_bytes());
141			frame_support::storage::unhashed::put(k.as_ref(), &i);
142		});
143
144		#[block]
145		{
146			assert_eq!(Map1M::<T>::get(1 << 9), Some(1 << 9));
147		}
148	}
149
150	// Reads from both storage maps each `n` and `m` times. Should result in two linear components.
151	#[benchmark]
152	fn storage_map_read_per_component(n: Linear<0, 100>, m: Linear<0, 100>) {
153		(0..m * 10).for_each(|i| Map1M::<T>::insert(i, i));
154		(0..n * 10).for_each(|i| Map16M::<T>::insert(i, i));
155
156		#[block]
157		{
158			(0..m).for_each(|i| assert_eq!(Map1M::<T>::get(i * 10), Some(i * 10)));
159			(0..n).for_each(|i| assert_eq!(Map16M::<T>::get(i * 10), Some(i * 10)));
160		}
161	}
162
163	#[benchmark(pov_mode = MaxEncodedLen {
164		Pov::Map1M: Ignored
165	})]
166	fn storage_map_read_per_component_one_ignored(n: Linear<0, 100>, m: Linear<0, 100>) {
167		(0..m * 10).for_each(|i| Map1M::<T>::insert(i, i));
168		(0..n * 10).for_each(|i| Map16M::<T>::insert(i, i));
169
170		#[block]
171		{
172			(0..m).for_each(|i| assert_eq!(Map1M::<T>::get(i * 10), Some(i * 10)));
173			(0..n).for_each(|i| assert_eq!(Map16M::<T>::get(i * 10), Some(i * 10)));
174		}
175	}
176
177	// Reads the same value from a storage map. Should not result in a component.
178	#[benchmark]
179	fn storage_1m_map_one_entry_repeated_read(n: Linear<0, 100>) {
180		Map1M::<T>::insert(0, 0);
181
182		#[block]
183		{
184			(0..n).for_each(|_| assert_eq!(Map1M::<T>::get(0), Some(0)));
185		}
186	}
187
188	// Reads the same values from a storage map. Should result in a `1x` linear component.
189	#[benchmark]
190	fn storage_1m_map_multiple_entry_repeated_read(n: Linear<0, 100>) {
191		(0..n).for_each(|i| Map1M::<T>::insert(i, i));
192
193		#[block]
194		{
195			(0..n).for_each(|i| {
196				// Reading the same value 10 times does nothing.
197				(0..10).for_each(|_| assert_eq!(Map1M::<T>::get(i), Some(i)));
198			});
199		}
200	}
201
202	#[benchmark]
203	fn storage_1m_double_map_read_per_component(n: Linear<0, 1024>) {
204		(0..(1 << 10)).for_each(|i| DoubleMap1M::<T>::insert(i, i, i));
205
206		#[block]
207		{
208			(0..n).for_each(|i| assert_eq!(DoubleMap1M::<T>::get(i, i), Some(i)));
209		}
210	}
211
212	#[benchmark]
213	fn storage_value_bounded_read() {
214		#[block]
215		{
216			assert!(BoundedValue::<T>::get().is_none());
217		}
218	}
219
220	// Reading unbounded values will produce no mathematical worst case PoV size for this component.
221	#[benchmark]
222	fn storage_value_unbounded_read() {
223		#[block]
224		{
225			assert!(UnboundedValue::<T>::get().is_none());
226		}
227	}
228
229	#[benchmark(pov_mode = Ignored)]
230	fn storage_value_unbounded_ignored_read() {
231		#[block]
232		{
233			assert!(UnboundedValue::<T>::get().is_none());
234		}
235	}
236
237	// Same as above, but we still expect a mathematical worst case PoV size for the bounded one.
238	#[benchmark]
239	fn storage_value_bounded_and_unbounded_read() {
240		(0..1024).for_each(|i| Map1M::<T>::insert(i, i));
241		#[block]
242		{
243			assert!(UnboundedValue::<T>::get().is_none());
244			assert!(BoundedValue::<T>::get().is_none());
245		}
246	}
247
248	#[benchmark(pov_mode = Measured)]
249	fn measured_storage_value_read_linear_size(l: Linear<0, { 1 << 22 }>) {
250		let v: sp_runtime::BoundedVec<u8, _> = alloc::vec![0u8; l as usize].try_into().unwrap();
251		LargeValue::<T>::put(&v);
252		#[block]
253		{
254			assert!(LargeValue::<T>::get().is_some());
255		}
256	}
257
258	#[benchmark(pov_mode = MaxEncodedLen)]
259	fn mel_storage_value_read_linear_size(l: Linear<0, { 1 << 22 }>) {
260		let v: sp_runtime::BoundedVec<u8, _> = alloc::vec![0u8; l as usize].try_into().unwrap();
261		LargeValue::<T>::put(&v);
262		#[block]
263		{
264			assert!(LargeValue::<T>::get().is_some());
265		}
266	}
267
268	#[benchmark(pov_mode = Measured)]
269	fn measured_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) {
270		let v: sp_runtime::BoundedVec<u8, _> = alloc::vec![0u8; l as usize].try_into().unwrap();
271		LargeValue::<T>::put(&v);
272		LargeValue2::<T>::put(&v);
273		#[block]
274		{
275			assert!(LargeValue::<T>::get().is_some());
276			assert!(LargeValue2::<T>::get().is_some());
277		}
278	}
279
280	#[benchmark(pov_mode = MaxEncodedLen)]
281	fn mel_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) {
282		let v: sp_runtime::BoundedVec<u8, _> = alloc::vec![0u8; l as usize].try_into().unwrap();
283		LargeValue::<T>::put(&v);
284		LargeValue2::<T>::put(&v);
285		#[block]
286		{
287			assert!(LargeValue::<T>::get().is_some());
288			assert!(LargeValue2::<T>::get().is_some());
289		}
290	}
291
292	#[benchmark(pov_mode = MaxEncodedLen {
293		Pov::LargeValue2: Measured
294	})]
295	fn mel_mixed_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) {
296		let v: sp_runtime::BoundedVec<u8, _> = alloc::vec![0u8; l as usize].try_into().unwrap();
297		LargeValue::<T>::put(&v);
298		LargeValue2::<T>::put(&v);
299		#[block]
300		{
301			assert!(LargeValue::<T>::get().is_some());
302			assert!(LargeValue2::<T>::get().is_some());
303		}
304	}
305
306	#[benchmark(pov_mode = Measured {
307		Pov::LargeValue2: MaxEncodedLen
308	})]
309	fn measured_mixed_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) {
310		let v: sp_runtime::BoundedVec<u8, _> = alloc::vec![0u8; l as usize].try_into().unwrap();
311		LargeValue::<T>::put(&v);
312		LargeValue2::<T>::put(&v);
313		#[block]
314		{
315			assert!(LargeValue::<T>::get().is_some());
316			assert!(LargeValue2::<T>::get().is_some());
317		}
318	}
319
320	#[benchmark(pov_mode = Measured)]
321	fn storage_map_unbounded_both_measured_read(i: Linear<0, 1000>) {
322		UnboundedMap::<T>::insert(i, alloc::vec![0; i as usize]);
323		UnboundedMap2::<T>::insert(i, alloc::vec![0; i as usize]);
324		#[block]
325		{
326			assert!(UnboundedMap::<T>::get(i).is_some());
327			assert!(UnboundedMap2::<T>::get(i).is_some());
328		}
329	}
330
331	#[benchmark(pov_mode = MaxEncodedLen {
332		Pov::UnboundedMap: Measured
333	})]
334	fn storage_map_partial_unbounded_read(i: Linear<0, 1000>) {
335		Map1M::<T>::insert(i, 0);
336		UnboundedMap::<T>::insert(i, alloc::vec![0; i as usize]);
337		#[block]
338		{
339			assert!(Map1M::<T>::get(i).is_some());
340			assert!(UnboundedMap::<T>::get(i).is_some());
341		}
342	}
343
344	#[benchmark(pov_mode = MaxEncodedLen {
345		Pov::UnboundedMap: Ignored
346	})]
347	fn storage_map_partial_unbounded_ignored_read(i: Linear<0, 1000>) {
348		Map1M::<T>::insert(i, 0);
349		UnboundedMap::<T>::insert(i, alloc::vec![0; i as usize]);
350		#[block]
351		{
352			assert!(Map1M::<T>::get(i).is_some());
353			assert!(UnboundedMap::<T>::get(i).is_some());
354		}
355	}
356
357	// Emitting an event will not incur any PoV.
358	#[benchmark]
359	fn emit_event() {
360		// Emit a single event.
361		let call = Call::<T>::emit_event {};
362		#[block]
363		{
364			call.dispatch_bypass_filter(RawOrigin::Root.into()).unwrap();
365		}
366		assert_eq!(System::<T>::events().len(), 1);
367	}
368
369	// A No-OP will not incur any PoV.
370	#[benchmark]
371	fn noop() {
372		let call = Call::<T>::noop {};
373		#[block]
374		{
375			call.dispatch_bypass_filter(RawOrigin::Root.into()).unwrap();
376		}
377	}
378
379	#[benchmark]
380	fn storage_iteration() {
381		for i in 0..65000 {
382			UnboundedMapTwox::<T>::insert(i, alloc::vec![0; 64]);
383		}
384		#[block]
385		{
386			for (key, value) in UnboundedMapTwox::<T>::iter() {
387				unsafe {
388					core::ptr::read_volatile(&key);
389					core::ptr::read_volatile(value.as_ptr());
390				}
391			}
392		}
393	}
394
395	impl_benchmark_test_suite!(Pallet, super::mock::new_test_ext(), super::mock::Test,);
396}
397
398#[cfg(test)]
399mod mock {
400	use frame_support::derive_impl;
401	use sp_runtime::{testing::H256, BuildStorage};
402
403	type AccountId = u64;
404	type Nonce = u32;
405
406	type Block = frame_system::mocking::MockBlock<Test>;
407
408	frame_support::construct_runtime!(
409		pub enum Test
410		{
411			System: frame_system,
412			Baseline: crate,
413		}
414	);
415
416	#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
417	impl frame_system::Config for Test {
418		type BaseCallFilter = frame_support::traits::Everything;
419		type BlockWeights = ();
420		type BlockLength = ();
421		type DbWeight = ();
422		type RuntimeOrigin = RuntimeOrigin;
423		type Nonce = Nonce;
424		type RuntimeCall = RuntimeCall;
425		type Hash = H256;
426		type Hashing = ::sp_runtime::traits::BlakeTwo256;
427		type AccountId = AccountId;
428		type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
429		type Block = Block;
430		type RuntimeEvent = RuntimeEvent;
431		type BlockHashCount = ();
432		type Version = ();
433		type PalletInfo = PalletInfo;
434		type AccountData = ();
435		type OnNewAccount = ();
436		type OnKilledAccount = ();
437		type SystemWeightInfo = ();
438		type SS58Prefix = ();
439		type OnSetCode = ();
440		type MaxConsumers = frame_support::traits::ConstU32<16>;
441	}
442
443	impl crate::Config for Test {
444		type RuntimeEvent = RuntimeEvent;
445	}
446
447	pub fn new_test_ext() -> sp_io::TestExternalities {
448		frame_system::GenesisConfig::<Test>::default().build_storage().unwrap().into()
449	}
450}