mcpField
M
motoko-benchmarks-generation

motoko-benchmarks-generation

github.com/research-ag测试与质量059 次安装9 周前更新
代码仓库

关于 motoko-benchmarks-generation

motoko-benchmarks-generation 是使用 bench-helper 编写 Motoko 基准测试的技能。它为每项测试定义 schema 和 run 函数,供运行器统一发现和执行,并说明配置方法与测量陷阱。

主要功能

  1. 01用 schema 和 run 函数定义基准测试
  2. 02每项测试使用独立的 .bench.mo 模块
  3. 03提供 mo:core 与 mo:base 示例
  4. 04配置优化后的 Wasm 测量
  5. 05规避初始化、变更与计时陷阱

适用场景

  1. 01为 Motoko 包添加可重复的基准测试
  2. 02按操作和输入类别测量性能
  3. 03配置 mops.toml 以运行优化基准测试
  4. 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:core Array.tabulate(size, gen) → mo:base Array.tabulate(size, gen)
    • mo:core Array.repeat(val, size) → mo:base Array.freeze(Array.init(size, val))(没有直接的不可变 repeat)
    • mo:core VarArray.tabulate(size, gen) → mo:base Array.tabulateVar(size, gen)
    • mo:core VarArray.repeat(val, size) → mo:base Array.init(size, val)(参数顺序相反) 完整映射请参阅 base-core-migration 技能。

工作原理

  • 模式
    • name 和 description 用于描述基准测试。
    • rows 列举要测量的不同操作或变体(例如,“编码”“解码”)。
    • cols 列举不同的输入类别(例如,消息大小)。
  • 运行器约定
    • 返回带版本号的记录 Bench.V1(schema, run);运行器会多次调用 run(rowIndex, colIndex), 以记录耗时。
    • 应使用 run 中的副作用/结果(例如 ignore ...),以防止死代码消除。

常见陷阱

  1. 行数/列数不匹配
    • 确保你的 routines 表的尺寸为 rows.size() x cols.size()。如果你添加或删除行/列标签,请相应更新 routines 的构建方式;否则某些单元格将不会执行任何操作。
  2. 在 run 中执行开销较大的设置
    • 在 init 中生成输入一次,并在闭包中捕获它们。只在 run 中执行核心操作。
  3. 忘记使用结果
    • 使用 ignore 来处理返回值;否则编译器可能会将该调用视为无用代码并将其删除。
  4. 非确定性和计时噪声
    • 避免在 run 中记录日志/打印或随机分配内存;让各行的 GC 压力保持相近。
  5. 重复运行和可变操作
    • 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,了解适用于你的工具链/版本的最新推荐运行器命令。

评论

支持 Markdown