motoko-benchmarks-generation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Motoko Benchmarks with bench‑helper

使用bench‑helper进行Motoko基准测试

What This Is

这是什么

bench-helper
is a tiny Motoko library that standardizes how to write benchmarks. You describe a benchmark using a small schema (name, rows, cols), provide a
run(row, col)
function, and return a versioned bench record. Each file under
bench/*.bench.mo
defines one benchmark module. A runner can then discover and execute all benches consistently.
bench-helper
是一个小型Motoko库,用于标准化基准测试的编写方式。你可以通过一个小型模式(名称、行、列)描述基准测试,提供一个
run(row, col)
函数,并返回一个带版本的基准测试记录。
bench/*.bench.mo
下的每个文件定义一个基准测试模块。运行器可以一致地发现并执行所有基准测试。

Prerequisites

前提条件

mops.toml (add dependencies and toolchain)
If you already have a
mops.toml
, just add
bench-helper
under
[dev-dependencies]
. If your project still uses
mo:base
instead of
mo:core
, you can keep it — benches themselves can be written with
mo:base
without affecting your runtime canisters.
mops.toml(添加依赖项和工具链)
如果你已经有
mops.toml
,只需在
[dev-dependencies]
下添加
bench-helper
。如果你的项目仍在使用
mo:base
而非
mo:core
,可以继续保留——基准测试本身可以用
mo:base
编写,不会影响你的运行时容器(canister)。

Directory & File Conventions

目录与文件约定

  • Put benches under
    bench/
    at the repo root.
  • Name files with the suffix
    .bench.mo
    , one benchmark per file; for example:
    bench/base64.bench.mo
    .
  • Each bench file is a Motoko
    module { ... }
    that exposes a single
    public func init() : Bench.V1
    function.
  • Inside
    init
    , construct a
    Bench.Schema
    and return
    Bench.V1(schema, run)
    where
    run : (rowIndex : Nat, colIndex : Nat) -> ()
    performs the measured operation.
Minimal skeleton
motoko
import Array "mo:core/Array";
import Text  "mo:core/Text";
import Bench "mo:bench-helper";

module {
  public func init() : Bench.V1 {
    let schema : Bench.Schema = {
      name = "My bench";
      description = "What this bench measures";
      rows = ["size 16", "size 64", "size 256"]; // your row labels
      columns = ["operation A", "operation B"];  // your column labels
    };

    // Prepare inputs outside of `run` so they are not re-created on every iteration
    let inputs : [[Nat8]] = [
      Array.init<Nat8>(16, 0),
      Array.init<Nat8>(64, 0),
      Array.init<Nat8>(256, 0),
    ];

    // Build a table of routines to measure: routines[row][col] : () -> ()
    let routines : [[() -> ()]] = Array.tabulate(
      rows.size(),
      func(ri) {
        let input = inputs[ri]; // capture precomputed input
        [
          func() { ignore input.size() },    // operation A @ inputs[ri]
          func() { ignore input.toArray() }, // operation B @ inputs[ri]
        ]
      },
    );

    // The runner calls this many times; keep it tiny and branch-free.
    Bench.V1(schema, func(ri : Nat, ci : Nat) = routines[ri][ci]());
  };
};
Note: if you're not using "core" dependency, replace "mo:core" imports with "mo:base"
  • 将基准测试放在仓库根目录的
    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) -> ()
    执行被测量的操作。
最小化骨架
motoko
import Array "mo:core/Array";
import Text  "mo:core/Text";
import Bench "mo:bench-helper";

module {
  public func init() : Bench.V1 {
    let schema : Bench.Schema = {
      name = "My bench";
      description = "What this bench measures";
      rows = ["size 16", "size 64", "size 256"]; // 你的行标签
      columns = ["operation A", "operation B"];  // 你的列标签
    };

    // 在`run`之外准备输入,避免每次迭代都重新创建
    let inputs : [[Nat8]] = [
      Array.init<Nat8>(16, 0),
      Array.init<Nat8>(64, 0),
      Array.init<Nat8>(256, 0),
    ];

    // 构建要测量的例程表:routines[row][col] : () -> ()
    let routines : [[() -> ()]] = Array.tabulate(
      rows.size(),
      func(ri) {
        let input = inputs[ri]; // 捕获预计算的输入
        [
          func() { ignore input.size() },    // 操作A @ inputs[ri]
          func() { ignore input.toArray() }, // 操作B @ inputs[ri]
        ]
      },
    );

    // 运行器会多次调用此函数;保持函数简洁且无分支。
    Bench.V1(schema, func(ri : Nat, ci : Nat) = routines[ri][ci]());
  };
};
注意:如果你没有使用“core”依赖项,请将“mo:core”导入替换为“mo:base”

How It Works

工作原理

  • Schema
    • name
      and
      description
      describe the bench.
    • rows
      enumerate different operations or variants you measure (e.g., "encode", "decode").
    • cols
      enumerate different input categories (e.g., message sizes).
  • Runner contract
    • You return a versioned record
      Bench.V1(schema, run)
      ; the runner calls
      run(rowIndex, colIndex)
      many times to record timings.
    • Side effects/results inside
      run
      should be consumed (e.g.,
      ignore ...
      ) to prevent dead‑code elimination.
  • 模式(Schema)
    • name
      description
      描述基准测试。
    • rows
      列举你要测量的不同操作或变体(例如:"encode"、"decode")。
    • cols
      列举不同的输入类别(例如:消息大小)。
  • 运行器约定
    • 你返回一个带版本的记录
      Bench.V1(schema, run)
      ;运行器会多次调用
      run(rowIndex, colIndex)
      以记录计时。
    • run
      内部的副作用/结果应该被消耗(例如:
      ignore ...
      ),以防止死代码消除。

Common Pitfalls

常见陷阱

  1. Rows/cols mismatch
    • Ensure your
      routines
      table has dimensions
      rows.size() x cols.size()
      . If you add or remove a row/col label, update how you build
      routines
      ; otherwise some cells will be no‑ops.
  2. Doing expensive setup inside
    run
    • Generate inputs once in
      init
      and capture them in closures. Only do the core operation in
      run
      .
  3. Forgetting to consume results
    • Use
      ignore
      to consume return values; otherwise the compiler might drop the call as dead code.
  4. Non‑determinism and timing noise
    • Keep
      run
      free of logging/printing and random allocation; keep GC pressure comparable across rows.
  1. 行/列不匹配
    • 确保你的
      routines
      表的维度为
      rows.size() x cols.size()
      。如果你添加或删除了行/列标签,请更新
      routines
      的构建方式;否则某些单元格将成为空操作。
  2. run
    内部执行昂贵的设置操作
    • init
      中生成一次输入,并在闭包中捕获它们。仅在
      run
      中执行核心操作。
  3. 忘记消耗结果
    • 使用
      ignore
      来消耗返回值;否则编译器可能会将调用视为死代码而丢弃。
  4. 非确定性和计时噪声
    • 确保
      run
      中没有日志/打印和随机分配操作;保持各行的GC压力相当。

More examples

更多示例

Verify It Works

验证运行效果

The exact runner/command may vary depending on your environment. After adding
bench-helper
to
mops.toml
and writing
.bench.mo
files:
  • Ensure your project resolves dependencies:
    bash
    mops install
  • Run benchmarks:
    bash
    mops bench
  • Consult the
    bench-helper
    package README for the latest recommended runner command for your toolchain/version.
具体的运行器/命令可能因环境而异。在将
bench-helper
添加到
mops.toml
并编写
.bench.mo
文件后:
  • 确保项目已解析依赖项:
    bash
    mops install
  • 运行基准测试:
    bash
    mops bench
  • 查阅
    bench-helper
    包的README文档,获取适用于你的工具链/版本的最新推荐运行器命令。