Polkadot SDK 自定义 Pallet Benchmark 指南:生成并接入 Weight

原文作者:PaperMoon团队

在 Polkadot SDK(FRAME)中,Benchmark(基准测试)指的是:测量 Pallet 的各个 Extrinsic(可调度调用)所消耗的计算资源,主要包括:

• 执行时间(Execution Time)

• 存储访问开销(Storage reads/writes)

这些测量结果会被用于生成 Weight(权重)。Weight 的准确性非常关键,因为它直接影响:

• 链处理交易的效率(决定一个区块能装多少"资源")

• 交易费计算(费用往往与 Weight 相关)

• 对 DoS(拒绝服务)攻击的防护能力(避免攻击者用低成本触发高消耗逻辑)

本指南将演示如何为 Pallet 编写 Benchmark,并把生成的权重结果接入到运行时(Runtime)中。

开始之前,请确保你已具备:

  1. 一个需要 Benchmark 的 Pallet

• 如果你跟随了 Pallet 开发教程,可以直接使用 Create a Pallet 章节的 Counter Pallet。

• 如果是其他 Pallet,则需要你在后续步骤中:

• 修改 benchmarking.rs 中各 benchmark 函数的测试逻辑

• 同步更新后续步骤里对这些函数的引用,确保生成的 Weight 与你的实际 Extrinsic 对应

  1. 对 计算复杂度(computational complexity)有基本理解

  2. 熟悉 Rust 的测试框架(Rust testing framework)

  3. 熟悉 Polkadot Omni Node 与 Polkadot Chain Spec Builder 的搭建流程

创建 Benchmarking 模块

在 Pallet 的 src 目录下创建一个新文件 benchmarking.rs,并加入如下代码:

文件路径:pallets/pallet-custom/src/benchmarking.rs

复制代码
#![cfg(feature = "runtime-benchmarks")]

use super::*;
use frame::deps::frame_benchmarking::v2::*;
use frame::benchmarking::prelude::RawOrigin;

#[benchmarks]
mod benchmarks {
    use super::*;

    #[benchmark]
    fn set_counter_value() {
        let new_value: u32 = 100;

        #[extrinsic_call]
        _(RawOrigin::Root, new_value);

        assert_eq!(CounterValue::<T>::get(), new_value);
    }

    #[benchmark]
    fn increment() {
        let caller: T::AccountId = whitelisted_caller();
        let amount: u32 = 50;

        #[extrinsic_call]
        _(RawOrigin::Signed(caller.clone()), amount);

        assert_eq!(CounterValue::<T>::get(), amount);
        assert_eq!(UserInteractions::<T>::get(caller), 1);
    }

    #[benchmark]
    fn decrement() {
        // First, set the counter to a non-zero value
        CounterValue::<T>::put(100);

        let caller: T::AccountId = whitelisted_caller();
        let amount: u32 = 30;

        #[extrinsic_call]
        _(RawOrigin::Signed(caller.clone()), amount);

        assert_eq!(CounterValue::<T>::get(), 70);
        assert_eq!(UserInteractions::<T>::get(caller), 1);
    }

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

补充说明(便于理解)

• 该模块包含 Pallet 的全部 Benchmark 定义。

• #[benchmark] 标记每一个基准测试函数,它通常对应某个 Extrinsic 的测量流程。

• #[extrinsic_call] (origin, ...) 是 v2 Benchmark 框架提供的调用写法, 指代当前 benchmark 对应的 extrinsic。

• whitelisted_caller() 用于生成"白名单账户",以减少与测试无关的额外开销干扰(让测量更聚焦在业务逻辑本身)。

• 末尾的 impl_benchmark_test_suite! 会为每个 benchmark 生成测试用例,便于你先用 cargo test 验证 benchmark 的编译与逻辑正确性。

如果你要 Benchmark 其他 Pallet,需要按你的业务逻辑修改断言(assert_eq!)部分,以验证该 Extrinsic 调用后状态是否符合预期。

定义 Weight Trait

为 Pallet 添加一个 weights 模块,并定义 WeightInfo trait。示例代码如下:

文件路径:pallets/pallet-custom/src/weights.rs

复制代码
#[frame::pallet]
pub mod pallet {
    use frame::prelude::*;
    pub use weights::WeightInfo;

    pub mod weights {
        use frame::prelude::*;

        pub trait WeightInfo {
            fn set_counter_value() -> Weight;
            fn increment() -> Weight;
            fn decrement() -> Weight;
        }

        impl WeightInfo for () {
            fn set_counter_value() -> Weight {
                Weight::from_parts(10_000, 0)
            }
            fn increment() -> Weight {
                Weight::from_parts(15_000, 0)
            }
            fn decrement() -> Weight {
                Weight::from_parts(15_000, 0)
            }
        }
    }

    // ... rest of pallet
}

解释

• WeightInfo 是权重接口(trait),用于抽象不同环境下的权重实现。

• impl WeightInfo for () 是占位权重(placeholder weights),主要用于开发阶段与测试阶段,让 Pallet 在尚未生成真实权重文件时也能正常编译运行。

• 如果你使用的是其他 Pallet,请将 trait 中的函数名替换为你 Pallet 中实际的 extrinsic 名称,确保一一对应。

在 Config 中加入 WeightInfo

更新 Pallet 的 Config trait,加入 WeightInfo:

文件路径:pallets/pallet-custom/src/lib.rs

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

    #[pallet::constant]
    type CounterMaxValue: Get<u32>;

    type WeightInfo: weights::WeightInfo;
}

为什么要把 WeightInfo 放进 Config?

WeightInfo 作为 Config 的关联类型(associated type),能让 runtime 在集成该 Pallet 时选择不同的权重实现,例如:

• 开发/测试:使用 () 的占位实现

• 生产:使用 benchmark 生成的真实权重实现

这样你无需修改 Pallet 源码,只需在 runtime 配置中切换即可。

更新 Extrinsic 的 weight 标注

将 extrinsic 上的权重标注从硬编码替换为 T::WeightInfo::...():

文件路径:pallets/pallet-custom/src/lib.rs

复制代码
#[pallet::call]
impl<T: Config> Pallet<T> {
    #[pallet::call_index(0)]
    #[pallet::weight(T::WeightInfo::set_counter_value())]
    pub fn set_counter_value(origin: OriginFor<T>, new_value: u32) -> DispatchResult {
        // ... implementation
    }

    #[pallet::call_index(1)]
    #[pallet::weight(T::WeightInfo::increment())]
    pub fn increment(origin: OriginFor<T>, amount: u32) -> DispatchResult {
        // ... implementation
    }

    #[pallet::call_index(2)]
    #[pallet::weight(T::WeightInfo::decrement())]
    pub fn decrement(origin: OriginFor<T>, amount: u32) -> DispatchResult {
        // ... implementation
    }
}

说明

通过 T::WeightInfo::function_name() 调用,而不是硬编码 Weight::from_parts(),可以让 extrinsic 自动使用 runtime 中配置的权重实现:

• 测试时可用占位权重

• 生产时可用真实测量权重

• 且无需改动 Pallet 代码本身

引入 Benchmarking 模块

lib.rs 文件顶部加入模块声明:

文件路径:pallets/pallet-custom/src/lib.rs

复制代码
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;
use alloc::vec::Vec;

pub use pallet::*;

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

// Additional pallet code

这里的 #[cfg(feature = "runtime-benchmarks")] 表示:只有启用 runtime-benchmarks feature 时才会编译 benchmark 代码,从而避免生产构建携带无关内容,保持 runtime 轻量高效。

配置 Pallet 依赖

更新 Pallet 的 Cargo.toml,开启 benchmark feature:

文件路径:pallets/pallet-custom/Cargo.toml

复制代码
[dependencies]
codec = { features = ["derive"], workspace = true }
scale-info = { features = ["derive"], workspace = true }
frame = { features = ["experimental", "runtime"], workspace = true }

[features]
default = ["std"]
runtime-benchmarks = [
    "frame/runtime-benchmarks",
]
std = [
    "codec/std",
    "scale-info/std",
    "frame/std",
]

说明

Rust 的 feature flag 机制允许按需编译。这里通过定义 runtime-benchmarks feature,并让其级联启用 frame/runtime-benchmarks,实现:

• benchmark 时:依赖完整、功能齐全

• 生产构建:不编译 benchmark 代码与相关依赖,减少体积与复杂度

更新 Mock Runtime

在测试用的 mock.rs 中加入 WeightInfo 配置:

文件路径:pallets/pallet-custom/src/mock.rs

复制代码
impl pallet_custom::Config for Test {
    type RuntimeEvent = RuntimeEvent;
    type CounterMaxValue = ConstU32<1000>;
    type WeightInfo = ();
}

配置 Runtime Benchmarking

要运行 benchmark,你的 Pallet 必须集成进 runtime 的 benchmark 基础设施中。

1)更新 runtime/Cargo.toml

把 Pallet 加入 runtime 的 runtime-benchmarks feature:

文件路径:runtime/Cargo.toml

复制代码
runtime-benchmarks = [
    "cumulus-pallet-parachain-system/runtime-benchmarks",
    "hex-literal",
    "pallet-parachain-template/runtime-benchmarks",
    "polkadot-sdk/runtime-benchmarks",
    "pallet-custom/runtime-benchmarks",
]

当你使用 --features runtime-benchmarks 构建 runtime 时,这会确保所有 pallet(包括你的 pallet)都包含必要的 benchmark 代码。

2)runtime 配置先使用占位权重

文件路径:runtime/src/configs/mod.rs

复制代码
impl pallet_custom::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type CounterMaxValue = ConstU32<1000>;
    type WeightInfo = ();
}

3)注册 benchmark 列表

将 pallet 加入 runtime 的 benchmark 注册文件:

文件路径:runtime/src/benchmarks.rs

复制代码
polkadot_sdk::frame_benchmarking::define_benchmarks!(
    [frame_system, SystemBench::<Runtime>]
    [pallet_balances, Balances]
    // ... other pallets
    [pallet_custom, CustomPallet]
);

define_benchmarks! 宏会生成 CLI 可发现的 benchmark 执行入口,使 benchmark 工具能够定位并运行你 Pallet 的 benchmark。

验证 Benchmark 编译与测试

运行以下命令确认 benchmark 能编译,并以测试形式执行:

复制代码
cargo test -p pallet-custom --features runtime-benchmarks

你会看到类似输出:

复制代码
test benchmarking::benchmarks::bench_set_counter_value ... ok
test benchmarking::benchmarks::bench_increment ... ok
test benchmarking::benchmarks::bench_decrement ... ok

这里的重点是:impl_benchmark_test_suite! 会为每个 benchmark 生成单元测试,提前发现编译错误或断言失败,避免你在构建整个 runtime 后才定位问题。

构建带 Benchmark 的 Runtime

使用启用 benchmark 的方式构建 runtime(生成 WASM):

复制代码
cargo build --release --features runtime-benchmarks

通常生成的 runtime WASM 文件位于:

复制代码
target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm

该构建会包含 benchmark 所需的 host functions 与基础设施,从而让 benchmark 工具可以在 WASM 执行环境中进行测量。后续真正上线生产时,你会使用不包含 benchmark 的构建产物。

安装 Benchmark 工具

安装官方工具 frame-omni-bencher:

复制代码
cargo install frame-omni-bencher --locked

frame-omni-bencher 是 Polkadot SDK 官方的 FRAME Pallet Benchmark 工具,用于标准化地:

• 执行 benchmarks

• 测量执行时间与存储操作

• 生成规范的 weight 文件,并与 FRAME weight 系统完整对接

下载 Weight 模板

下载官方 weight 模板文件(Handlebars 模板):

bash 复制代码
curl -L https://raw.githubusercontent.com/paritytech/polkadot-sdk/refs/tags/polkadot-stable2512/substrate/.maintain/frame-weight-template.hbs \
--output ./pallets/pallet-custom/frame-weight-template.hbs

模板的作用说明

weight 模板用于将 benchmark 的原始测量数据转换成格式正确的 Rust 源码文件(通常是 weights.rs),并定义生成文件的结构,包括:

• imports

• trait 定义与实现

• 文档注释

• 统一格式排版

• benchmark 参数、存储读写次数、硬件信息等元数据

使用官方模板可以确保输出符合 Polkadot SDK 的规范与风格。

执行 Benchmarks 并生成 weights.rs

运行 benchmark,生成权重文件:

bash 复制代码
frame-omni-bencher v1 benchmark pallet \
    --runtime ./target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm \
    --pallet pallet_custom \
    --extrinsic "" \
    --template ./pallets/pallet-custom/frame-weight-template.hbs \
    --output ./pallets/pallet-custom/src/weights.rs

为什么要基于 WASM 进行 Benchmark?

因为区块链生产环境中真正运行的是 WASM runtime,而不是本地 native。WASM 的执行特性(编译、沙盒、运行开销)可能与 native 不同,因此基于 WASM 测得的权重更贴近真实链上情况。

使用生成的权重

运行完成后,会生成 weights.rs 文件,里面包含基于真实硬件测量得到的权重数据,能更准确反映:

• 逻辑复杂度

• 存储访问模式

• 具体计算开销

1)在 Pallet 中引入 weights 模块

文件路径:pallets/pallet-custom/src/lib.rs

bash 复制代码
#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;
use alloc::vec::Vec;

pub use pallet::*;

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

pub mod weights;

#[frame::pallet]
pub mod pallet {
    use super::*;
    use frame::prelude::*;
    use crate::weights::WeightInfo;
    // ... rest of pallet
}

注意:benchmarking 模块只在跑 benchmark 时需要;但 weights 模块必须在所有构建中可用,因为 runtime 日常执行需要调用权重函数来计算手续费和执行区块资源限制。

2)在 runtime 中启用生成的权重实现

将 runtime 配置从 () 替换为生成的权重类型:

文件路径:runtime/src/configs/mod.rs

bash 复制代码
impl pallet_custom::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type CounterMaxValue = ConstU32<1000>;
    type WeightInfo = pallet_custom::weights::SubstrateWeight<Runtime>;
}

完成后,你的生产 runtime 将使用真实测量的权重来:

• 计算交易费用

• 约束区块资源消耗

• 提升系统整体的资源计量准确性与安全性

示例:生成的 weight 文件

原文只提示"示例生成文件"。实际生成内容会根据你的硬件与代码行为变化,因此以你本地生成的 ./pallets/pallet-custom/src/weights.rs 为准。

完成

至此,你已经成功完成:

• 为 Pallet 编写并验证 benchmark

• 构建带 benchmark 的 runtime WASM

• 使用 frame-omni-bencher 生成权重文件

• 在 runtime 中启用生成的真实权重

这意味着你的 Pallet 在性能计量与交易资源约束方面已达到更接近生产可用的状态。

原文链接:https://docs.polkadot.com/parachains/customize-runtime/pallet-development/benchmark-pallet/

相关推荐
综合热讯6 小时前
股票融资融券交易时间限制一览与制度说明
大数据·人工智能·区块链
爱吃生蚝的于勒6 小时前
【Linux】线程概念(一)
java·linux·运维·服务器·开发语言·数据结构·vim
数据知道6 小时前
PostgreSQL 故障排查:紧急排查与 SQL 熔断处理(CPU 占用 100% 等情况)
数据库·sql·postgresql
静听山水6 小时前
Redis的Pipeline (管道)
数据库·redis·php
Pluchon6 小时前
硅基计划4.0 算法 简单模拟实现位图&布隆过滤器
java·大数据·开发语言·数据结构·算法·哈希算法
君鼎6 小时前
计算机网络第九章:无线网络与移动网络学习总结
网络·计算机网络
我命由我123456 小时前
Java 泛型 - Java 泛型通配符(上界通配符、下界通配符、无界通配符、PECS 原则)
java·开发语言·后端·java-ee·intellij-idea·idea·intellij idea
yunsr6 小时前
python作业3
开发语言·python
数据知道6 小时前
PostgreSQL 性能优化: I/O 瓶颈分析,以及如何提高数据库的 I/O 性能?
数据库·postgresql·性能优化