Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy

.nornir/benching.md

Benchmarking: why nornir bench, not cargo bench

znippy has exactly one benchmark suite, and it does not run through cargo's bench harness. This chapter records why, and how the single source of truth is wired, so nobody re-adds a parallel criterion or #[bench] path.

Has nornir forked its own way? Yes — deliberately.

nornir does not use cargo's #[bench] / cargo bench / criterion harness at all. From nornir/src/bench/api.rs, the contract is tiny:

⚠ Correction, 2026-08-03 — there is no git_sha column. This chapter used to say the row is "tagged with release_id + per-repo git SHA + dep-graph snapshot", and so does nornir/.nornir/bencher.md. READ: nornir/src/warehouse/iceberg_schema.rs:127-140 gives bench_runs exactly seven fields — run_id, repo, ts_micros, date, version, machine, cores — and build_bench_runs_batch (nornir/src/warehouse/iceberg.rs:2440-2456) writes exactly those seven. No git_sha, no release_id. A bench number is keyed by (repo, machine, version, timestamp); SHA attribution is indirect, through release_lineage / regression_trace. Do not design a suite around per-SHA keying it will not get.

Why a fork, not cargo bench/criterion

  1. dev-deps. An example may use [dev-dependencies] (osm-katana, znippy-compress, the plugins); a bin may not — and nothing about nornir is allowed to leak into a published crate. That is the whole reason the entry point is an example, not a bin or a [[bench]].
  2. Different goal than criterion. criterion does statistical micro-sampling with its own human-readable output. nornir wants single-shot, real-workload numbers (multi-GB archives, the planet OSM pipeline), historized by git SHA, gated for >10% regression, and rendered into the README. criterion's model and cargo's bench output are not machine-historizable the way nornir needs.

The xtask pattern (this repo)

znippy is a multi-crate workspace, so it uses nornir's recommended shape:

xtask/                       publish = false  → heavy deps never reach crates.io
├── Cargo.toml               nornir + osm-katana + znippy-* under [dev-dependencies]
├── src/lib.rs               doc-only; exists so the package has a target the example builds against
└── examples/nornir-bench.rs the ONE bench source: registers every Bencher, calls run_main_json()

Run a release-gate-equivalent locally:

cargo run -p xtask --release --example nornir-bench            # full suite (JSON to stdout)
NORNIR_BENCH_SKIP_PLANET=1 NORNIR_BENCH_SKIP_HEAVY=1 \
  cargo run -p xtask --release --example nornir-bench          # skip the multi-GB workloads

The old before_cratering.sh and xtask/src/main.rs runners (which duplicated each other) and the throughput tests in tests/tests/perf_bench.rs / compress_dir_bench.rs were retired — their unique workloads were ported into nornir-bench.rs so nothing was lost:

Maven correctness tests stayed in tests/tests/maven_bench.rs; only the timing functions moved.

The git package format (znippy.git_*) — where its workloads live

Six arms measure the git package format: git_oid_lookup_{100k,1m,8m}, git_graph_{linear,merges}_200k, git_reach_10k, git_sections_300k and git_archive_60k. The registrations are in xtask/examples/nornir-bench.rs like every other arm, but the workloads are not — they live in plugins/native/znippy-plugin-git/src/bench_kernels.rs behind the crate's bench-kernels feature, and the Bencher impls are thin callers.

That is deliberate, and it is the direct consequence of the worktree trap above: no git worktree of znippy can build xtask at all, so a workload written in nornir-bench.rs could only ever be executed from /home/rickard/git/znippy. Written in the crate it compiles anywhere, cargo test -p znippy-plugin-git --features bench-kernels guards it, and there is still exactly one implementation (LAW 5). The feature is off by default — it is what drags in the fst rival and znippy-compress, and none of that belongs in a shipped archiver.

git_oid_lookup is the arm that matters: __gunnar_oid__ chose an stree keyspace over the stock fst trie and lookup_batch_pipeline::<8> was the whole justification, so the arm times binary search, stree serial, stree batch and an fst::Map built over the same oids — after cross-checking that all four return the same row for every query. The verdict is reported as a neutral *_x ratio rather than asserted, so a loss is visible instead of failing the run. The archive arm writes its fixture to /home/rickard/scratch/… (md1), never /tmp, which on oden is RAM and would have measured the page cache.

The cargo bench muscle-memory — wired

cargo bench -p xtask runs the exact same suite, via a thin [[bench]] shim. A [[bench]] is a separate binary, so it must link the same register_bench! registrations — xtask/benches/nornir.rs gets that by #[path]-including the example source (benches may use dev-deps, like examples), so inventory collects every bencher into the bench binary too:

// xtask/benches/nornir.rs
#[allow(dead_code)]
#[path = "../examples/nornir-bench.rs"]
mod suite;

fn main() -> anyhow::Result<()> {
    nornir::bench::api::run_main_json()
}
# xtask/Cargo.toml
[[bench]]
name    = "nornir"
harness = false

Caveats: it still emits nornir's JSON, not pretty criterion output, and nornir itself keeps calling cargo run --example (it never calls cargo bench). So the shim is only the familiar command with nornir semantics — not a second bench system. See nornir's .nornir/bencher.md → Surfaces for the canonical writeup.