Skip to main content
Version: 3.x (unreleased)

mops bench

Run Motoko benchmarks.

mops bench [filter]

Put your benchmark code in bench/*.bench.mo files.

It is necessary to use bench package to write benchmarks.

The output format is a markdown table, so you can copy-paste it into your README.md.

How it works

Benchmarks run on PocketIC, which Mops downloads and manages itself — dfx is not involved and does not need to be installed. Pin a version with mops toolchain use pocket-ic <version>; with no pin, Mops uses the default version it ships with.

Under the hood, Mops will:

  • Start a PocketIC server on an ephemeral port
  • Wrap each *.bench.mo file in a canister
  • Compile canisters under enhanced orthogonal persistence (moc's default) with the --force-gc flag and deploy them
  • Run each cell of the benchmark file as an update call (or a query call with --query)
  • For each call measure usage of wasm instructions(performance_counter) and heap size(rts_heap_size)
Instruction counts depend on how the wasm was built

The number you get is for the exact wasm PocketIC runs, and nothing post-optimizes it on deploy:

  • With [optimize]mops bench runs wasm-opt on the module before deploy (same pass as mops build). Prefer this when you want bench numbers to match an optimized deploy artifact. Pass --no-optimize to skip the pass for a single run without editing mops.toml.
  • Without [optimize] — PocketIC runs the raw moc output, with no optimization.

Always compare runs made with the same moc version, the same PocketIC version, and the same [optimize] settings. Different replicas report different instruction and heap counts, so baselines recorded before mops 3.0.0 on the dfx replica are not comparable — re-record them with --save.

If wasm-opt fails, mops warns and keeps the unoptimized module. Run with --verbose for details.

Options

--gc

Select garbage collector.

Possible values:

  • incremental (default)
  • copying
  • compacting
  • generational

Under enhanced orthogonal persistence (the default persistence mode), moc fixes the GC to incremental and the collector cannot be chosen — the other collectors only exist under legacy persistence. Selecting copying, compacting, or generational therefore implies --legacy-persistence.

--save

Save benchmark results to .bench/<filename>.json file.

--compare

Compare benchmark results with the results from .bench/<filename>.json file.

--query

Measure each cell in a query call instead of an update call.

This reflects how query methods actually execute on the IC: queries run no garbage collection, so the instruction counts exclude GC work that an update would incur. Use it to benchmark read-only/query workloads realistically.

Only works for benchmarks whose runner is synchronous — a runner that performs inter-canister (await) calls needs the update path and must be run without --query.

--legacy-persistence

Compile benchmark canisters under legacy persistence instead of enhanced orthogonal persistence (the default).

Use it to measure a canister that still uses legacy persistence. Has no effect with moc < 0.15, where legacy persistence is already the default.

--no-optimize

Skip the [optimize] wasm-opt pass for this run, even when it is configured in mops.toml. Has no effect when [optimize] is not set. PocketIC then runs the raw moc output.

mops bench --no-optimize

--verbose

Print the benchmark pipeline up front — compiler version, replica + version, GC, context (query/update), persistence, profile, and whether the wasm is optimized — then log the full moc build command and stream the compiler and replica output instead of hiding it.

-- <moc flags>

Pass extra flags directly to the Motoko compiler for this invocation. Appended after [moc].args from mops.toml.

mops bench -- -Werror

--locked

Require an up-to-date mops.lock and never write it — fails if the lockfile is missing or no longer matches mops.toml and the registry. Intended for CI, so that a job can run this command without a preceding mops install. See mops install --locked.