// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! All benchmarks in this file are just for debugging the PoV calculation logic, they are unused. #![cfg(feature = "runtime-benchmarks")] use super::*; use pezframe_benchmarking::v2::*; use pezframe_support::traits::UnfilteredDispatchable; use pezframe_system::{Pezpallet as System, RawOrigin}; use pezsp_runtime::traits::Hash; #[benchmarks] mod benchmarks { use super::*; #[benchmark] fn storage_single_value_read() { Value::::put(123); #[block] { assert_eq!(Value::::get(), Some(123)); } } #[benchmark(pov_mode = Ignored)] fn storage_single_value_ignored_read() { Value::::put(123); #[block] { assert_eq!(Value::::get(), Some(123)); } } #[benchmark(pov_mode = MaxEncodedLen { Pov::Value2: Ignored })] fn storage_single_value_ignored_some_read() { Value::::put(123); Value2::::put(123); #[block] { assert_eq!(Value::::get(), Some(123)); assert_eq!(Value2::::get(), Some(123)); } } #[benchmark] fn storage_single_value_read_twice() { Value::::put(123); #[block] { assert_eq!(Value::::get(), Some(123)); assert_eq!(Value::::get(), Some(123)); } } #[benchmark] fn storage_single_value_write() { #[block] { Value::::put(123); } assert_eq!(Value::::get(), Some(123)); } #[benchmark] fn storage_single_value_kill() { Value::::put(123); #[block] { Value::::kill(); } assert!(!Value::::exists()); } // This benchmark and the following are testing a storage map with adjacent storage items. // // First a storage map is filled and a specific number of other storage items is // created. Then the one value is read from the map. This demonstrates that the number of other // nodes in the Trie influences the proof size. The number of inserted nodes can be interpreted // as the number of `StorageMap`/`StorageValue` in the whole runtime. #[benchmark(pov_mode = Measured)] fn storage_1m_map_read_one_value_two_additional_layers() { (0..(1 << 10)).for_each(|i| Map1M::::insert(i, i)); // Assume there are 16-256 other storage items. (0..(1u32 << 4)).for_each(|i| { let k = T::Hashing::hash(&i.to_be_bytes()); pezframe_support::storage::unhashed::put(k.as_ref(), &i); }); #[block] { assert_eq!(Map1M::::get(1 << 9), Some(1 << 9)); } } #[benchmark(pov_mode = Measured)] fn storage_1m_map_read_one_value_three_additional_layers() { (0..(1 << 10)).for_each(|i| Map1M::::insert(i, i)); // Assume there are 256-4096 other storage items. (0..(1u32 << 8)).for_each(|i| { let k = T::Hashing::hash(&i.to_be_bytes()); pezframe_support::storage::unhashed::put(k.as_ref(), &i); }); #[block] { assert_eq!(Map1M::::get(1 << 9), Some(1 << 9)); } } #[benchmark(pov_mode = Measured)] fn storage_1m_map_read_one_value_four_additional_layers() { (0..(1 << 10)).for_each(|i| Map1M::::insert(i, i)); // Assume there are 4096-65536 other storage items. (0..(1u32 << 12)).for_each(|i| { let k = T::Hashing::hash(&i.to_be_bytes()); pezframe_support::storage::unhashed::put(k.as_ref(), &i); }); #[block] { assert_eq!(Map1M::::get(1 << 9), Some(1 << 9)); } } // Reads from both storage maps each `n` and `m` times. Should result in two linear components. #[benchmark] fn storage_map_read_per_component(n: Linear<0, 100>, m: Linear<0, 100>) { (0..m * 10).for_each(|i| Map1M::::insert(i, i)); (0..n * 10).for_each(|i| Map16M::::insert(i, i)); #[block] { (0..m).for_each(|i| assert_eq!(Map1M::::get(i * 10), Some(i * 10))); (0..n).for_each(|i| assert_eq!(Map16M::::get(i * 10), Some(i * 10))); } } #[benchmark(pov_mode = MaxEncodedLen { Pov::Map1M: Ignored })] fn storage_map_read_per_component_one_ignored(n: Linear<0, 100>, m: Linear<0, 100>) { (0..m * 10).for_each(|i| Map1M::::insert(i, i)); (0..n * 10).for_each(|i| Map16M::::insert(i, i)); #[block] { (0..m).for_each(|i| assert_eq!(Map1M::::get(i * 10), Some(i * 10))); (0..n).for_each(|i| assert_eq!(Map16M::::get(i * 10), Some(i * 10))); } } // Reads the same value from a storage map. Should not result in a component. #[benchmark] fn storage_1m_map_one_entry_repeated_read(n: Linear<0, 100>) { Map1M::::insert(0, 0); #[block] { (0..n).for_each(|_| assert_eq!(Map1M::::get(0), Some(0))); } } // Reads the same values from a storage map. Should result in a `1x` linear component. #[benchmark] fn storage_1m_map_multiple_entry_repeated_read(n: Linear<0, 100>) { (0..n).for_each(|i| Map1M::::insert(i, i)); #[block] { (0..n).for_each(|i| { // Reading the same value 10 times does nothing. (0..10).for_each(|_| assert_eq!(Map1M::::get(i), Some(i))); }); } } #[benchmark] fn storage_1m_double_map_read_per_component(n: Linear<0, 1024>) { (0..(1 << 10)).for_each(|i| DoubleMap1M::::insert(i, i, i)); #[block] { (0..n).for_each(|i| assert_eq!(DoubleMap1M::::get(i, i), Some(i))); } } #[benchmark] fn storage_value_bounded_read() { #[block] { assert!(BoundedValue::::get().is_none()); } } // Reading unbounded values will produce no mathematical worst case PoV size for this component. #[benchmark] fn storage_value_unbounded_read() { #[block] { assert!(UnboundedValue::::get().is_none()); } } #[benchmark(pov_mode = Ignored)] fn storage_value_unbounded_ignored_read() { #[block] { assert!(UnboundedValue::::get().is_none()); } } // Same as above, but we still expect a mathematical worst case PoV size for the bounded one. #[benchmark] fn storage_value_bounded_and_unbounded_read() { (0..1024).for_each(|i| Map1M::::insert(i, i)); #[block] { assert!(UnboundedValue::::get().is_none()); assert!(BoundedValue::::get().is_none()); } } #[benchmark(pov_mode = Measured)] fn measured_storage_value_read_linear_size(l: Linear<0, { 1 << 22 }>) { let v: pezsp_runtime::BoundedVec = alloc::vec![0u8; l as usize].try_into().unwrap(); LargeValue::::put(&v); #[block] { assert!(LargeValue::::get().is_some()); } } #[benchmark(pov_mode = MaxEncodedLen)] fn mel_storage_value_read_linear_size(l: Linear<0, { 1 << 22 }>) { let v: pezsp_runtime::BoundedVec = alloc::vec![0u8; l as usize].try_into().unwrap(); LargeValue::::put(&v); #[block] { assert!(LargeValue::::get().is_some()); } } #[benchmark(pov_mode = Measured)] fn measured_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) { let v: pezsp_runtime::BoundedVec = alloc::vec![0u8; l as usize].try_into().unwrap(); LargeValue::::put(&v); LargeValue2::::put(&v); #[block] { assert!(LargeValue::::get().is_some()); assert!(LargeValue2::::get().is_some()); } } #[benchmark(pov_mode = MaxEncodedLen)] fn mel_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) { let v: pezsp_runtime::BoundedVec = alloc::vec![0u8; l as usize].try_into().unwrap(); LargeValue::::put(&v); LargeValue2::::put(&v); #[block] { assert!(LargeValue::::get().is_some()); assert!(LargeValue2::::get().is_some()); } } #[benchmark(pov_mode = MaxEncodedLen { Pov::LargeValue2: Measured })] fn mel_mixed_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) { let v: pezsp_runtime::BoundedVec = alloc::vec![0u8; l as usize].try_into().unwrap(); LargeValue::::put(&v); LargeValue2::::put(&v); #[block] { assert!(LargeValue::::get().is_some()); assert!(LargeValue2::::get().is_some()); } } #[benchmark(pov_mode = Measured { Pov::LargeValue2: MaxEncodedLen })] fn measured_mixed_storage_double_value_read_linear_size(l: Linear<0, { 1 << 22 }>) { let v: pezsp_runtime::BoundedVec = alloc::vec![0u8; l as usize].try_into().unwrap(); LargeValue::::put(&v); LargeValue2::::put(&v); #[block] { assert!(LargeValue::::get().is_some()); assert!(LargeValue2::::get().is_some()); } } #[benchmark(pov_mode = Measured)] fn storage_map_unbounded_both_measured_read(i: Linear<0, 1000>) { UnboundedMap::::insert(i, alloc::vec![0; i as usize]); UnboundedMap2::::insert(i, alloc::vec![0; i as usize]); #[block] { assert!(UnboundedMap::::get(i).is_some()); assert!(UnboundedMap2::::get(i).is_some()); } } #[benchmark(pov_mode = MaxEncodedLen { Pov::UnboundedMap: Measured })] fn storage_map_partial_unbounded_read(i: Linear<0, 1000>) { Map1M::::insert(i, 0); UnboundedMap::::insert(i, alloc::vec![0; i as usize]); #[block] { assert!(Map1M::::get(i).is_some()); assert!(UnboundedMap::::get(i).is_some()); } } #[benchmark(pov_mode = MaxEncodedLen { Pov::UnboundedMap: Ignored })] fn storage_map_partial_unbounded_ignored_read(i: Linear<0, 1000>) { Map1M::::insert(i, 0); UnboundedMap::::insert(i, alloc::vec![0; i as usize]); #[block] { assert!(Map1M::::get(i).is_some()); assert!(UnboundedMap::::get(i).is_some()); } } // Emitting an event will not incur any PoV. #[benchmark] fn emit_event() { // Emit a single event. let call = Call::::emit_event {}; #[block] { call.dispatch_bypass_filter(RawOrigin::Root.into()).unwrap(); } assert_eq!(System::::events().len(), 1); } // A No-OP will not incur any PoV. #[benchmark] fn noop() { let call = Call::::noop {}; #[block] { call.dispatch_bypass_filter(RawOrigin::Root.into()).unwrap(); } } #[benchmark] fn storage_iteration() { for i in 0..65000 { UnboundedMapTwox::::insert(i, alloc::vec![0; 64]); } #[block] { for (key, value) in UnboundedMapTwox::::iter() { unsafe { core::ptr::read_volatile(&key); core::ptr::read_volatile(value.as_ptr()); } } } } impl_benchmark_test_suite!(Pezpallet, super::mock::new_test_ext(), super::mock::Test,); } #[cfg(test)] mod mock { use pezframe_support::derive_impl; use pezsp_runtime::{testing::H256, BuildStorage}; type AccountId = u64; type Nonce = u32; type Block = pezframe_system::mocking::MockBlock; pezframe_support::construct_runtime!( pub enum Test { System: pezframe_system, Baseline: crate, } ); #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)] impl pezframe_system::Config for Test { type BaseCallFilter = pezframe_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::pezsp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = pezsp_runtime::traits::IdentityLookup; type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); type PalletInfo = PalletInfo; type AccountData = (); type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); type MaxConsumers = pezframe_support::traits::ConstU32<16>; } impl crate::Config for Test { type RuntimeEvent = RuntimeEvent; } pub fn new_test_ext() -> pezsp_io::TestExternalities { pezframe_system::GenesisConfig::::default() .build_storage() .unwrap() .into() } }