关于 motoko-benchmarks-generation
motoko-benchmarks-generation 是使用 bench-helper 编写 Motoko 基准测试的技能。它为每项测试定义 schema 和 run 函数,供运行器统一发现和执行,并说明配置方法与测量陷阱。
主要功能
- 01用 schema 和 run 函数定义基准测试
- 02每项测试使用独立的 .bench.mo 模块
- 03提供 mo:core 与 mo:base 示例
- 04配置优化后的 Wasm 测量
- 05规避初始化、变更与计时陷阱
适用场景
- 01为 Motoko 包添加可重复的基准测试
- 02按操作和输入类别测量性能
- 03配置 mops.toml 以运行优化基准测试
- 04排查维度不匹配或输入被修改的问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ research-ag/ motoko-skills --skill motoko-benchmarks-generation安装后在 Claude Code 中输入/motoko-benchmarks-generation确认已加载。
详细介绍
使用 bench‑helper 进行 Motoko 基准测试
这是什么
bench-helper 是一个小型 Motoko 库,用于统一编写基准测试的方式。
你使用一个小型模式(名称、行、列)描述基准测试,提供一个 run(row, col)
函数,并返回一个带版本号的基准测试记录。bench/*.bench.mo 下的每个文件都定义一个
基准测试模块。然后,运行器可以发现并一致地执行所有基准测试。
前提条件
mops.toml(添加依赖项和工具链)
如果你已有 mops.toml,只需在 [dev-dependencies] 下添加 bench-helper。
如果你的项目仍使用 mo:base 而不是 mo:core,可以继续使用它——基准测试本身可以使用
mo:base 编写,不会影响运行时 canister。
确保已配置 [optimize]。 只有当 mops.toml 中包含
[optimize] 部分时,mops bench 才会运行 Binaryen 的 wasm-opt——即使该部分为空也会启用它(默认 level = "O3")。如果没有该配置,mops bench 测量的是
未优化的 Wasm,无法反映生产 canister 的实际运行情况。[optimize] 要求固定
[toolchain] wasm-opt 版本,例如:
[optimize]
[toolchain]
wasm-opt = "132" # mops toolchain use wasm-opt <version>
motoko-mops-package-maintenance 技能会在维护期间自动为包含基准测试的包添加此配置——如果尚未运行该技能,请先自行添加,再相信基准测试数据。
目录和文件约定
- 将基准测试放在仓库根目录下的
bench/中。 - 文件名以
.bench.mo为后缀,每个文件包含一个基准测试;例如:bench/base64.bench.mo。 - 每个基准测试文件都是一个 Motoko
module { ... },其中公开一个public func init() : Bench.V1函数。 - 在
init中,构造一个Bench.Schema,并返回Bench.V1(schema, run),其中run : (rowIndex : Nat, colIndex : Nat) -> ()执行被测操作。
最简骨架
import Array "mo:core/Array";
import Bench "mo:bench-helper";
module {
/// One benchmark per file; `mops bench` calls `init()` to obtain the
/// schema and the run function.
public func init() : Bench.V1 {
let schema : Bench.Schema = {
name = "My bench";
description = "What this bench measures";
rows = ["size 16", "size 64", "size 256"]; // input categories
cols = ["operation A", "operation B"]; // operations to measure
};
// Prepare inputs once in `init` so they are not re-created on every call.
// `Array.tabulate` returns an immutable `[Nat8]`; use `VarArray.repeat`
// (or `VarArray.tabulate`) if you need a `[var Nat8]` instead.
let inputs : [[Nat8]] = [
Array.tabulate<Nat8>(16, func _ = 0),
Array.tabulate<Nat8>(64, func _ = 0),
Array.tabulate<Nat8>(256, func _ = 0),
];
// Build a table of routines: routines[ri][ci] : () -> ()
let routines : [[() -> ()]] = Array.tabulate<[() -> ()]>(
schema.rows.size(),
func(ri) {
let input = inputs[ri]; // capture precomputed input
[
func() { ignore input.size() }, // operation A @ inputs[ri]
func() { ignore input[0] }, // operation B @ inputs[ri]
];
},
);
// `Bench.V1` is a *class* (instance type), not a record alias.
// The runner calls the second argument many times; keep it tiny
// and branch-free.
Bench.V1(schema, func(ri : Nat, ci : Nat) = routines[ri][ci]());
};
};
注意:
Schema字段是cols,不是columns。rows.size()也一样——你 必须写schema.rows.size()(裸标识符rows不在作用域内)。mo:core/Array没有init函数。要构造不可变的[T],请使用Array.tabulate<T>(size, gen)或Array.repeat<T>(val, size)。要构造 可变的[var T],请导入VarArray "mo:core/VarArray",并使用VarArray.tabulate<T>(size, gen)或VarArray.repeat<T>(val, size); 此类输入应标注为[var T](例如,表格中的元素类型为[[var Nat8]])。- 如果你不使用
mo:core,请将mo:core导入替换为mo:base。 注意,mo:base/Array同时包含不可变和可变数组——没有单独的VarArray模块。 两者的接口并不完全相同;对于 Array API,相关的对应关系如下:mo:coreArray.tabulate(size, gen)→mo:baseArray.tabulate(size, gen)mo:coreArray.repeat(val, size)→mo:baseArray.freeze(Array.init(size, val))(没有直接的不可变repeat)mo:coreVarArray.tabulate(size, gen)→mo:baseArray.tabulateVar(size, gen)mo:coreVarArray.repeat(val, size)→mo:baseArray.init(size, val)(参数顺序相反) 完整映射请参阅base-core-migration技能。
工作原理
- 模式
name和description用于描述基准测试。rows列举要测量的不同操作或变体(例如,“编码”“解码”)。cols列举不同的输入类别(例如,消息大小)。
- 运行器约定
- 返回带版本号的记录
Bench.V1(schema, run);运行器会多次调用run(rowIndex, colIndex), 以记录耗时。 - 应使用
run中的副作用/结果(例如ignore ...),以防止死代码消除。
- 返回带版本号的记录
常见陷阱
- 行数/列数不匹配
- 确保你的
routines表的尺寸为rows.size() x cols.size()。如果你添加或删除行/列标签,请相应更新routines的构建方式;否则某些单元格将不会执行任何操作。
- 确保你的
- 在
run中执行开销较大的设置- 在
init中生成输入一次,并在闭包中捕获它们。只在run中执行核心操作。
- 在
- 忘记使用结果
- 使用
ignore来处理返回值;否则编译器可能会将该调用视为无用代码并将其删除。
- 使用
- 非确定性和计时噪声
- 避免在
run中记录日志/打印或随机分配内存;让各行的 GC 压力保持相近。
- 避免在
- 重复运行和可变操作
bench-helper会对每个单元格多次调用run(ri, ci),以收集计时样本。如果被测操作会修改捕获的输入(例如原地排序、原地更新哈希),那么从第二次调用开始,看到的将是操作后的状态——对于原地排序,就是上一次调用后已排好序的数组;对于原地哈希更新,就是上一次调用留下的累加器。你可以在run中重新构建输入(并接受将分配成本计入测量),也可以注明基准测试测量的是对已修改/操作后状态的重复运行,而非对新准备的输入进行测量。
更多示例
bench-helper 参考基准测试:https://github.com/research-ag/bench-helper/tree/main/bench
验证其是否正常工作
具体的运行器/命令可能因环境而异。在将 bench-helper 添加到 mops.toml 并编写
.bench.mo 文件后:
- 确保项目能够解析依赖项:
mops install - 运行基准测试:
mops bench - 请参阅
bench-helper软件包的 README,了解适用于你的工具链/版本的最新推荐运行器命令。