pallet进阶(12)-benchmarking

benchmarking应该都很熟悉,性能测试,同时该测试可以为pallet的公共方法提供权重值

1. 概述

  1. 编写benchmarking分两种情况:
    1. 对函数进行性能测试时需要的构造条件不会涉及到本pallet以外的其它pallet;
    2. 在对函数进行性能测试时需要先使用其它的pallet构造测试的先决条件。

2. 编写pallet业务代码

在lib.rs中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod mock;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

pub mod weights;
pub use weights::WeightInfo;

pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use codec::Codec;
use frame_support::{
pallet_prelude::*, sp_runtime::traits::AtLeast32BitUnsigned, sp_std::fmt::Debug,
};
use frame_system::pallet_prelude::*;
use scale_info::prelude::vec::Vec;

use crate::WeightInfo;
use sp_io::hashing::{blake2_128, twox_128};

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;

//声明StudentNumber类型
type StudentNumberType: Member
+ Parameter
+ AtLeast32BitUnsigned
+ Codec
+ From<u32>
+ Into<u32>
+ Copy
+ Debug
+ Default
+ MaxEncodedLen
+ MaybeSerializeDeserialize;

//声明StudentName类型
type StudentNameType: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ From<u128>
+ Into<u128>
+ Copy
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ Debug;

type WeightInfo: WeightInfo;
}

// 4. Runtime Storage
// 用storageMap存储学生信息,(key, value)分别对应的是学号和姓名.
#[pallet::storage]
#[pallet::getter(fn students_info)]
pub type StudentsInfo<T: Config> =
StorageMap<_, Blake2_128Concat, T::StudentNumberType, T::StudentNameType, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
SetStudentInfo(T::StudentNumberType, T::StudentNameType),
}

#[pallet::error]
pub enum Error<T> {
// 相同学号的只允许设置一次名字
SetStudentsInfoDuplicate,
}

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(<T as Config>::WeightInfo::set_student_info((*student_number).into() ))]
pub fn set_student_info(origin: OriginFor<T>, student_number: T::StudentNumberType, student_name: T::StudentNameType, ) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
if StudentsInfo::<T>::contains_key(student_number) {
return Err(Error::<T>::SetStudentsInfoDuplicate.into())
}
StudentsInfo::<T>::insert(&student_number, &student_name);
Self::deposit_event(Event::SetStudentInfo(student_number, student_name));
Self::generate_key();
Ok(().into())
}
}

impl<T: Config> Pallet<T> {
fn generate_key() {
// let key1 = twox_128(b"Balances".to_vec().as_slice());
let key1 = twox_128(b"Balances");
let key2 = twox_128(b"FreeBalance");
let key3 = Self::blake2_128_concat(
b"0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
);
log::info!(target: "use-benchmarking", "------------ ============================, key1 = {:?}, key2 = {:?}, key3 = {:?}", key1, key2, key3);
let a = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY".as_bytes().to_vec();
a.using_encoded(|ref slice| {
log::info!(target: "use-benchmarking", "------------ ============================, code = {:?}", slice);
});
}

fn blake2_128_concat(d: &[u8]) -> Vec<u8> {
let mut v = blake2_128(d).to_vec();
v.extend_from_slice(d);
v
}
}
}

3. 编写mock代码

mock.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
use crate as pallet_use_benchmarking;

use frame_support::traits::{ConstU16, ConstU64};
use frame_system as system;
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
};

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
type Block = frame_system::mocking::MockBlock<Test>;

frame_support::construct_runtime!(
pub enum Test where
Block = Block,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system,
UseBenchmarkingDemo: pallet_use_benchmarking,
}
);

impl system::Config for Test {
type BaseCallFilter = frame_support::traits::Everything;
type BlockWeights = ();
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = Event;
type BlockHashCount = ConstU64<250>;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = ();
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ConstU16<42>;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}

impl pallet_use_benchmarking::Config for Test {
type Event = Event;
type StudentNumberType = u32;
type StudentNameType = u128;
}

pub use frame_support::pallet_prelude::GenesisBuild;

pub fn new_test_ext() -> sp_io::TestExternalities {
system::GenesisConfig::default().build_storage::<Test>().unwrap().into()
}

4. 编写benchmarking

编写benchmarking的目的主要是为调度函数生成对应的权重计算函数,对应到本例子中就主要是use-benchmarking的set_student_info函数生成对应的权重计算函数。
编写benchmark以宏benchmarks!包含,里面的内容主要分三部分,做的事情分别是准备条件、调用调度函数、对执行结果验证。对于第一步,其实就是对我们调度函数中的变量进行赋值,
benchmarking.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use super::*;

#[allow(unused)]
use crate::Pallet as UseBenchmarkingDemo;
use frame_benchmarking::{benchmarks, whitelisted_caller};
use frame_system::RawOrigin;

benchmarks! {
set_student_info {
let s in 0 .. 100;
let caller: T::AccountId = whitelisted_caller();
}:{
let _ = UseBenchmarkingDemo::<T>::set_student_info(RawOrigin::Signed(caller).into(), s.into(), Default::default());
}
verify {
assert_eq!(<StudentsInfo<T>>::get::<<T as pallet::Config>::StudentNumberType>(s.into()), Default::default());
}

impl_benchmark_test_suite!(UseBenchmarkingDemo, crate::mock::new_test_ext(), crate::mock::Test);
}

5. 添加到runtime中

添加依赖.pallet/runtime/Cargo.toml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[dependencies]
#...
pallet-use-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../pallets/use-benchmarking" }
#...

[features]
default = ["std"]
std = [
#...
"pallet-use-benchmarking/std",
#...
]

runtime-benchmarks = [
#...
"pallet-use-benchmarking/runtime-benchmarks",
#...
]

然后就是将use-benchmarking添加到runtime中,在runtime/src/lib.rs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
impl pallet_use_benchmarking::Config for Runtime {
type Event = Event;
type StudentNumberType = u32;
type StudentNameType = u128;
}

construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
//...
//添加下面这行
UseBenchmarkingDemo: pallet_use_benchmarking,
}
);

将use-benchmarking pallet添加到对应的benchmark宏中,在runtime/src/lib.rs:

1
2
3
4
5
6
7
8
9

#[cfg(feature = "runtime-benchmarks")]
mod benches {
define_benchmarks!(
//...
//这行为添加的代码
[pallet_use_benchmarking, UseBenchmarkingDemo]
);
}

6. 编译&生成weights.rs文件

编译

1
2
# 需要带上--features runtime-benchmarks
cargo build --features runtime-benchmarks

拷贝模板:

1
2
3
4
5
mkdir .maintain
cd .maintain
#此处的substrate同官方的repo仓库
#从substrate的repo中拷贝模板放到我们的substrate-node-template目录下
cp substrate/.maintain/frame-weight-template.hbs .

生成weights文件

1
2
3
4
5
6
./target/debug/node-template benchmark --chain dev \
--execution wasm --wasm-execution compiled \
--pallet pallet_use_benchmarking \
--extrinsic "*" --steps 20 --repeat 10 \
--output ./pallets/use-benchmarking/src/weights.rs \
--template ./.maintain/frame-weight-template.hbs

执行完后就会在./pallets/use-benchmarking/src/目录下生成对应的weights.rs文件。
至此,生成了weights.rs文件,但是这仅仅只是生成,还需要把生成的权重函数用到pallet中

7. 将生成的权重函数应用到pallet中

首先需要在pallet的Cargo.toml中添加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

...
[dependencies]
...
#添加下面这行
sp-std = { default-features = false,
version = "4.0.0",
git = "https://github.com/paritytech/substrate.git",
branch = "polkadot-v0.9.28" }

[features]
default = ["std"]
std = [
...
#添加下面这行
"sp-std/std",
]

接着在pallet的lib.rs中:
#[pallet::weight(100)]改为#[pallet::weight(<T as Config>::WeightInfo::set_student_info((*student_number).into() ))]
最后,还需要回到runtime/src/lib.rs文件,修改pallet_use_benchmarking的配置,如下:

1
2
3
4
5
impl pallet_use_benchmarking::Config for Runtime {
//...
//添加这一行
type WeightInfo = pallet_use_benchmarking::weights::SubstrateWeight<Runtime>;
}

重新编译,pallet的对应的权重就会在新的执行文件中生效:

1
cargo build

8. 总结

本文编辑完毕

  • Copyrights © 2017-2023 Jason
  • Visitors: | Views:

谢谢打赏~

微信