What Is benchfmt.rs? The Beginner's Guide to Go-Style Benchmarking in Rust

CodexFuse Team · 2026-11-05T15:20:48.288Z · 29 min read min read

What Is benchfmt.rs? The Beginner's Guide to Go-Style Benchmarking in Rust

What Is benchfmt.rs?

benchfmt.rs is a zero-dependency Rust benchmark runner that brings Go's benchstat workflow to Rust. It is a new Rust crate by Bartek Plotka, announced on November 3, 2026, and it does two things: it runs your benchmarks natively with a benchfmt::Suite runner, and it converts existing Criterion and libtest output into Go's benchfmt format so the benchstat tool can analyze it. The point is simple: Rust developers get the same A/B comparison tables Go developers have used for years, including heap tracking and pprof profiling, without rewriting their benchmarks.

The name needs one clarification up front, because three different things share it. Bare "benchfmt" is Go's benchmark output format, defined by proposal 14313 in 2016 and implemented by the golang.org/x/perf/benchfmt package. benchfmt.rs is the new Rust crate that emits that same format from Rust code. A few unrelated older Go packages also use the benchfmt name, and none of them is this crate.

This article always means the Rust crate when it says benchfmt.rs.

benchfmt.rs is three days old at the time of writing: the repository was created on November 2, 2026, the announcement post appeared on November 3, and a Hacker News story followed on November 4. It has 24 stars, 0 forks, and 2 open issues on GitHub, no releases or version number yet, and installation is from git only. That youth matters, and this article treats the crate as an experiment to watch, not an established standard.

Every capability below comes from the author's own post and the crate's README, and every performance number is the author's own measurement, not an independent test.

The benchfmt.rs rust crate is 3 days old, the announcement post is 2 days old, and the Hacker News story is 1 day old.

Why would a Rust developer want a Go workflow at all? Rust's built-in #[bench] attribute is unstable and only available on the nightly channel, so stable Rust has no standard benchmark runner. Criterion.rs fills that gap well, but it cannot track heap allocations, it samples with a linear ramp instead of steady-state epochs, and it compares benchmarks pairwise instead of pivoting across dimensions. benchfmt.rs closes exactly those gaps, and it does it by speaking a format that benchstat already understands.

If you run benchmarks on a server you control, start with our guide to VPS hosting, which explains the stable single-tenant machine a fair benchmark comparison needs. And if you archive benchmark result files and want to prove a later comparison used the exact same file, a SHA-256 hash generator gives you that checksum in seconds.

Factbenchfmt.rs at a glance
What it isZero-dependency Rust benchmark runner (benchfmt::Suite) plus a benchfmt CLI that converts Criterion and libtest output to Go's benchfmt format
AuthorBartek Plotka (@bwplotka), ex-Go, Prometheus, and Thanos, author of the book "Efficient Go"
Repository createdNovember 2, 2026 (3 days before this article)
AnnouncedNovember 3, 2026, in the author's blog post on bwplotka.dev
LicenseApache-2.0
GitHub state24 stars, 0 forks, 2 open issues, no releases or tags, install from git only (snapshot November 5, 2026)
What it adds/key=value projections, heap tracking in B/op and allocs/op, pprof profiling, and the same CLI flags as go test -bench
Works onStable Rust for the Suite runner and the Criterion converter; only the libtest converter needs nightly cargo bench output
Self-hostNot applicable: it is a library and a CLI you run on your own machine, not a service
Community receptionOne Hacker News story, 30 points and 7 comments as of November 5, 2026, with mixed reactions

Why Rust Needed a Go-Style Benchmarking Workflow

Rust's standard benchmarking story has a hole in it. The #[bench] attribute lives in the internal test crate behind #![feature(test)], which means it only works on the nightly channel. The Cargo Book points stable users at crates on crates.io, "such as Criterion", instead of offering a built-in runner.

So every stable Rust project that benchmarks either depends on Criterion or builds its own harness, and neither path gives you Go's benchstat tables.

Criterion.rs is a statistics-driven micro-benchmarking library, a Rust port of Haskell's Criterion library. It collects and stores statistics from run to run, detects regressions, and generates an HTML report with plots under target/criterion. Its default run warms up for 3 seconds and collects 100 samples, and it supports named baselines so you can compare a run against a saved one.

That is a strong tool, and benchfmt.rs does not try to replace it.

The gap is what Criterion cannot see. It never records heap allocations, so a change that makes code faster by allocating more memory is invisible to it. It samples on a linear ramp, which mixes cold and warm runs into one distribution and inflates variance on microsecond loops.

And its comparisons are pairwise: one baseline folder against one new folder. It cannot pivot a table across languages or sample sizes the way benchstat can, and it cannot put Go and Rust results side by side in one table.

Go solved this years ago with a small, composable toolchain. Benchmarks print lines in the benchfmt format, and benchstat reads those files, computes medians with 95 percent confidence intervals, and runs a Mann-Whitney U test to say whether a change is real. The author's 2024 post on the Go workflow is referenced in the README as background, and the new crate ports that whole flow to Rust.

The rust benchmark runner question, then, is not "which library is fastest" but "which workflow lets you trust the answer", and the Go workflow's answer is statistics over many independent runs.

benchfmt.rs is a rust microbenchmarks benchstat bridge in exactly that sense. It gives Rust the runner, the format, and the converter, and it leaves the statistics to benchstat, a tool Go teams have trusted for a decade. The rest of this article walks through each piece: the format, the statistics, the install, the two ways to use it, and the numbers the author measured with it.

How the Go benchfmt Format Works

The Go benchfmt format is a plain UTF-8 text format defined by proposal 14313, written by Russ Cox and Austin Clements and last updated in February 2016. A file has three kinds of lines: configuration lines, benchmark result lines, and unit metadata lines. Configuration lines look like goos: linux or cpu: Intel Xeon 2.20GHz and describe the machine and the run.

Result lines carry the measurements. Metadata lines describe the units.

A result line reads like this: BenchmarkDecodeVarints/samples=2000/lang=rust-2 279861 8514 ns/op 563.52 MB/s 0 B/op 0 allocs/op. The name starts with Benchmark plus an uppercase letter, then slash-separated /key=value pairs, then a trailing -2 for the CPU count. After the name come the iteration count and one or more value-unit pairs.

The standard units are ns/op, MB/s, B/op, and allocs/op, and custom metrics are possible through Go's ReportMetric.

The /key=value pairs are the format's real power. They are per-benchmark configuration, so samples=2000/lang=rust and samples=2000/lang=go are two dimensions of the same benchmark, not two different benchmarks. benchstat can later pivot the table on any of those keys with its -col flag, which is how one file produces a Go-versus-Rust table or a 200-sample-versus-2000-sample table. The crate's Suite writes these labels natively, and its converter adds them to Criterion output too.

Unit metadata lines state which direction is better. The defaults are that ns/op, B/op, and allocs/op are better when lower, while MB/s is better when higher. That metadata is what lets benchstat color a table and say whether a change is an improvement or a regression without guessing.

The format is deliberately boring: text lines, no binary, no database, so any tool can read it and any version control system can diff it.

Line typeExampleWhat it means
Configurationgoos: linuxDescribes the run: operating system, architecture, package, CPU model
Configurationgoarch: amd64Architecture the benchmarks ran on
Configurationpkg: github.com/example/decodeThe package the benchmarks belong to
ResultBenchmarkDecodeVarints-2 279861 8514 ns/opOne benchmark: iteration count, then value-unit pairs
Result with labelsBenchmarkDecodeVarints/samples=2000/lang=rust-2Same benchmark with /key=value dimensions for benchstat pivots
Result with heap1643970 B/op 19297 allocs/opBytes and objects allocated per operation, when the runner tracks them
Unit metadataUnit ns/op better=lowerStates which direction counts as an improvement for that unit

What benchstat Does With the Output

benchstat computes statistical summaries and A/B comparisons of Go benchmarks. You hand it two or more files in benchfmt format, usually a "before" and an "after", and it prints a table of medians with confidence intervals. It is the tool that turns raw timing numbers into a defensible claim that one version is faster than another.

The statistics are deliberately conservative. benchstat computes the median and the 95 percent confidence interval for each benchmark, then compares the two sets with a Mann-Whitney U test and reports a p-value. The default significance threshold, alpha, is 0.05. Each benchmark should be run at least 10 times, and ideally 20, because a single run of a microbenchmark is mostly noise.

It also reports a geometric mean across the benchmarks, so one table summarizes a whole suite.

Reading a benchstat table is a skill, and the columns are the whole lesson. The first number is the old median, the second is the new median, and the delta column is the percentage change with its confidence interval. A p-value near 0.000 means the difference is almost certainly real, while a p-value above 0.05 means benchstat cannot tell the two apart, and it prints a tilde instead of a delta.

The author's own tables follow this shape exactly, and the "What the Author Actually Measured" section below reads them line by line.

The -col flag is where the /key=value labels pay off. benchstat -col /lang results/go.txt results/rust.txt pivots the table so Go and Rust are columns of the same rows, instead of two separate tables you compare by eye. The same trick works for -col /samples, which compares the 200-series and 2000-series workloads in one view. That is the go benchstat rust workflow in one command: one format, one tool, any language that can emit the format.

Table elementWhat it meansHow to read it
Median (old vs new)The middle value of each run set, in ns/op or ms/opLower is faster for time; compare the two medians, not single runs
Delta with ± intervalPercentage change plus the 95 percent confidence intervalA delta of -42.56% ± 1% is a real speedup; a wide interval means noisy data
p-valueProbability the difference is chance, from the Mann-Whitney U testBelow 0.05 counts as significant; above it, benchstat prints ~ instead of a delta
nNumber of runs per benchmark in each fileAt least 10, ideally 20; the author's tables use n=6 epochs, which is thin but stated
geomeanGeometric mean of the deltas across all benchmarksOne number for the whole suite; -26.12% means the suite is faster overall
-col /langPivots the table on the lang labelGo and Rust become columns of the same rows
-col /samplesPivots the table on the samples label200-series and 2000-series workloads compare in one view

How to Install benchfmt.rs and Run Your First Benchmark

Installation is from git only, because the crate has no releases and no version number yet. The README documents one command for the CLI, and a dev-dependency entry for the library. Both pull straight from the repository's main branch, so you are always on the newest commit, for better and for worse.

That is normal for a three-day-old crate, and it is the first thing to check again before you adopt it: the install story may change the moment a first release lands.

Your first run follows five steps, and the snippet below is the whole flow. Install the CLI, add the library to your benchmark target, write a Suite benchmark with the same flags Go developers use, run it with a fixed bench time and several epochs, and feed the output file to benchstat. The commands table after the list gives you the exact invocations the author documents.

  1. Install the CLI from git with cargo install --git https://github.com/bwplotka/benchfmt.rs.
  2. Add the library as a dev-dependency from git, with the pprof feature if you want profiling.
  3. Write a benchmark with benchfmt::Suite, using a while b.keep_running() loop like Go's b.Loop().
  4. Run it with a fixed bench time and several epochs, for example 2 seconds times 6 epochs, which is 12 seconds per benchmark.
  5. Compare the output with benchstat, pivoting on the label you care about, for example benchstat -col /lang.

Two details deserve attention. First, the benchmark target needs harness = false, because the Suite is its own harness and the default libtest harness would fight it. Second, the Suite parses the same flags as go test -bench: -bench, -benchtime, -count, -cpu, -benchmem, -cpuprofile, -memprofile, and -memprofilerate.

If you know Go's flags, you already know this CLI.

To convert an existing Criterion suite instead, run benchfmt criterion target/criterion --pkg my-pkg --cpu 2, which reads benchmark.json and sample.json from target/criterion. To convert nightly libtest output, run benchfmt libtest.

The Two Ways to Use benchfmt.rs: Suite Runner vs Criterion Converter

benchfmt.rs offers two paths, and the README is explicit about when to pick each. The Suite runner is for new benchmarks, or for benchmarks you are willing to rewrite. The converter is for codebases that already use Criterion and do not want to touch their benchmark code.

Both end in the same place: benchfmt files that benchstat can compare.

The Suite is a replacement runner. You register benchmarks with benchfmt::Suite, and it runs each one in calibrated steady-state epochs: a fixed -benchtime, such as 2 seconds, times a -count of independent epochs, such as 6. That mirrors Go's testing.B, and it is the reason the Suite's numbers are tighter than Criterion's.

The author's measurement puts the Suite's worst-case variance at ±1 to 2 percent, against ±5 percent for Criterion's ramp.

The converter is the pragmatic path. The standalone benchfmt CLI reads benchmark.json and sample.json directly from target/criterion, extracts Throughput::BytesDecimal (or Throughput::Bytes) as decimal MB/s, and groups Criterion's 100 samples into 6 contiguous epochs with the --epochs 6 flag, or about 16.67 samples per epoch. One command, benchfmt criterion target/criterion, turns an existing suite into benchstat input without touching the benchmark implementation.

There is one honest caveat, and the README states it plainly. Converted epochs are approximate: Criterion's samples come from a linear ramp, so the early buckets average shorter cold runs and the later buckets average longer warm runs. The Suite's epochs are exact independent runs, so its p-values are exact too, while the converter's p-values are approximate.

If you need publication-grade statistics, run the Suite; if you need a quick comparison of a suite you already have, the converter is the right tool. The libtest converter has a similar limit: one nightly cargo bench run gives a single summary line per benchmark, so you get no multiple epochs or p-values unless you concatenate repeated runs.

Choosing between them is a question of trust, and the author says so himself: if you do not trust a new benchmarking runtime yet, and you should not from the start, use the converter and keep your Criterion suite untouched. That is the criterion to benchstat migration path in one sentence. You get the benchstat tables today, and you can adopt the Suite later, when the crate has earned that trust.

AspectSuite runnerCriterion converter
Who it is forNew benchmarks, or suites you are willing to rewriteCodebases that already use Criterion and keep their code untouched
How it runsCalibrated steady-state epochs: fixed -benchtime times -count independent runsReads target/criterion output and groups 100 samples into 6 epochs
VarianceWorst case ±1 to 2 percent, per the author's measurement±5 percent, because the linear ramp mixes cold and warm runs
Heap trackingYes, B/op and allocs/op from CountingAllocNo: Criterion never recorded allocations, so the columns stay empty
p-valuesExact, from independent epochsApproximate, because the epochs are buckets of a ramp, not independent runs
InstallLibrary from git, benchmark target with harness = falseCLI from git, one command against target/criterion
When to pick itWhen you want the tightest numbers and the full feature setWhen you want benchstat tables today without rewriting anything

How Heap Tracking Works in benchfmt.rs

Heap tracking is the feature that makes benchfmt.rs more than a format converter. The Suite counts allocations with a CountingAlloc global allocator, set with #[global_allocator], that wraps your real allocator and counts every allocation. It reports two numbers per operation: B/op, the bytes allocated, and allocs/op, the number of allocation calls.

Those are the same two columns Go developers get from b.ReportAllocs, and they are the columns Criterion cannot produce at all.

The counter itself is small and dependency-free. It is a thread-local Cell<(u64, u64)> that counts alloc, alloc_zeroed, and realloc calls on the calling thread. It wraps custom allocators too, so you can keep jemalloc or mimalloc underneath it with CountingAlloc::with_allocator(...).

That design keeps the crate zero-dependency, which matters for a benchmarking library: every dependency you pull into a benchmark harness is a variable you cannot control.

There is one limitation to know. The counter is thread-local, so it counts on the calling thread only. Multi-threaded code, such as a tokio multi-thread runtime or a rayon workload, needs a process-wide atomic AllocTracker passed to Suite::with_alloc instead.

That is a documented constraint, not a bug, and the README states it in the scope section. If your benchmarks are single-threaded, the default just works.

Why do the two numbers matter? Time alone can lie. A change that makes code faster by allocating more memory looks like a pure win in ns/op, until the garbage collector or the allocator pays for it under load.

The author's own measurements show the trap clearly: on one workload, Rust was faster but allocated about 31 percent more bytes per operation than Go. Without B/op and allocs/op, that trade-off would have been invisible, and the "faster" claim would have been incomplete.

What the Author Actually Measured: Go vs Rust

The announcement post benchmarks two workloads on one machine: a 24-core Intel Xeon 2.20 GHz Linux workstation, Go 1.26.8, and Rust 1.99.1. The workloads are a varint decoder, a 36-line unsigned LEB128 decoder over a fixture of 2,000 uint64 values in 4,798 bytes, and a Prometheus Remote Write 2.0 decoder over Snappy-compressed payloads, with 200-series and 2000-series batches. The 2000-series batch is 244.9 KiB uncompressed and 57.0 KiB Snappy-compressed, which is a 4.30 times compression ratio.

All benchmark code and raw outputs live in the bwplotka/benchmarks repository under benchmarks/rust-go-decode.

The headline result, from the native Suite with 6 epochs of 2 seconds at 2 CPUs, is that Rust was faster on every workload. Varints ran in 4.902 microseconds in Rust against 8.536 microseconds in Go, a difference of 3.634 microseconds, or about 42.6 percent faster with a p-value of 0.002. The Remote Write 2000-series workload ran in 2.033 milliseconds in Rust against 2.470 milliseconds in Go, a difference of 0.437 milliseconds, or about 17.7 percent faster.

The geometric mean across the suite was -26.12 percent, and throughput told the same story: varints hit 933.3 MiB/s in Rust against 536.1 MiB/s in Go, a 74.09 percent improvement.

The Criterion-converted numbers agree closely, which is the point of the converter. Converting the same Criterion runs gave varints at 5.364 microseconds in Rust against 8.536 in Go, or -37.16 percent, and a suite geometric mean of -22.87 percent. Comparing the Suite directly against Criterion on the same Rust code, the Suite measured varints 8.60 percent faster than Criterion did, with a geometric mean difference of -4.21 percent.

The author attributes that gap to Criterion's 100-batch ramp, which adds about 8.6 percent overhead on microsecond loops and inflates variance.

The allocation table is where the story gets interesting, and it is the reason heap tracking exists. On the Remote Write 2000-series workload, Rust allocated 2.053 MiB per operation against Go's 1.568 MiB, or about 31 percent more bytes. But Rust made 16.49 thousand allocations per operation against Go's 19.30 thousand, or about 14.6 percent fewer allocation calls.

Fewer, larger allocations: that is a real trade-off, and it is invisible in any tool that only measures time. The author traces the extra bytes to prost vector growth, with 88.4 percent of Rust's allocated bytes coming from RawVec growth, including 23.0 percent from labels_refs growth and 21.3 percent from Vec<TimeSeries> growth.

One more check keeps the numbers honest. The author verified the microbenchmarks against standalone binaries under /usr/bin/time -v, and the standalone numbers agreed: Rust varints at 5.74 microseconds of wall time against Go's 8.64, and the Remote Write 2000-series at 2.22 milliseconds and 4.21 MiB of maximum RSS in Rust against 2.97 milliseconds and 15.98 MiB in Go. The 15.98 divided by 4.21 is 3.80 times more memory for Go, which sits inside the author's summary claim of 1.9 to 3.2 times less CPU and 3.8 to 4.4 times less RAM per request for Rust across the whole process.

Read these as one author's results on his machine and his workloads, not as a general claim that Rust is 42 percent faster than Go.

Workload (benchstat, n=6, -col /lang)GoRust (Suite)Change
DecodeVarints, time8.536 µs4.902 µs-42.56% (p=0.002)
DecodeRemoteWrite2 / 200-series, time271.4 µs231.5 µs-14.71%
DecodeRemoteWrite2 / 2000-series, time2.470 ms2.033 ms-17.68%
Suite geometric mean, time——-26.12%
DecodeVarints, throughput536.1 MiB/s933.3 MiB/s+74.09%
DecodeRemoteWrite2 / 2000-series, B/op1.568 MiB/op2.053 MiB/op+30.98% more bytes
DecodeRemoteWrite2 / 2000-series, allocs/op19.30k allocs/op16.49k allocs/op-14.55% fewer allocations

What pprof Profiling Showed

Benchmarks tell you that something is slow. Profiles tell you why. benchfmt.rs integrates pprof behind an optional feature flag, with [profile.bench] debug = 1 in your Cargo configuration and the flags -cpuprofile, -memprofile, and -memprofilerate. The memory profile rate defaults to 524288, or 512 KiB, which is the same default as Go's runtime.MemProfileRate, and a rate of 1 records every allocation.

The output files are .pb.gz, which open in go tool pprof -http=:8080 or on pprof.me. Custom profilers are possible through the Profiler trait and Suite::with_profiler.

The author's profiles explain both workloads. On the varint decoder, Go burned 1.74 seconds, or 32.3 percent of its time, on a single shift line, and 1.24 seconds, or 23.0 percent, on outer-loop checks. On the Remote Write 2000-series workload, Go lost 54.1 percent of its on-CPU time, about 1.34 milliseconds per operation, to runtime.mallocgc and GC assist over 19,297 heap objects.

That is the allocation story from the previous section, seen from the inside: nearly a fifth of the objects Go allocates are the price of the workload, and the garbage collector collects that price on every run.

Rust's profile points somewhere else. The Remote Write workload spent 71.1 percent of its CPU time in prost field decoding, 8.9 percent in snap decompression, and 18.7 percent in RawVecInner::grow_amortized, which is vector growth. The profile and the allocation table agree: Rust's cost is not garbage collection, it is decoding plus growing vectors that double their capacity.

That is a fixable cost, and it is exactly the kind of finding a benchmark plus a profile is for. Without the profile, you would know Rust allocates more; with it, you know where, and what to change.

benchfmt.rs vs Criterion: What Is Different?

The benchfmt.rs vs criterion question has a short answer: they are different tools for different jobs, and the crate is built to work with Criterion, not against it. Criterion is a statistics-driven benchmarking library with HTML reports, SVG plots, and named baselines. benchfmt.rs is a runner and a format bridge whose output feeds benchstat. The comparison table below lays out the differences, and the choice follows from what you need to measure.

Criterion's strengths are its statistics and its reporting. It collects samples run to run, detects regressions, and produces a report with PDF, regression, violin, and t-test plots under target/criterion/report/index.html. Its baselines let you save a run and compare against it later with --save-baseline and --baseline.

For a team that wants a readable report of one codebase over time, that is the right tool, and benchfmt.rs does not try to reproduce it.

benchfmt.rs wins where Criterion is blind. It tracks heap allocations, it samples in steady-state epochs instead of a linear ramp, and it emits a format that benchstat can pivot across languages and dimensions. It also costs far less to adopt: the author measured that pulling in Criterion with default features off still compiles 42 transitive crates, including clap, regex-automata, ciborium, oorandom, itertools, tinytemplate, and anes, and adds about 19 seconds to a cold cargo bench build. benchfmt.rs has zero dependencies.

That is not a small difference on a cold build, and it is the reason the crate's README leads with it.

The practical answer is to use both. Keep your Criterion suite, run the converter, and get benchstat tables today. If the tables prove useful and the crate matures, adopt the Suite for new benchmarks and get the heap columns and the tighter variance.

The two tools are not rivals; the converter exists precisely because the author expects most users to start from Criterion.

AspectCriterion.rsbenchfmt.rs Suite
What it isStatistics-driven micro-benchmarking library, a Rust port of Haskell's CriterionZero-dependency benchmark runner plus a benchfmt format converter
Sampling3-second warmup, then 100 samples on a linear rampCalibrated steady-state epochs: fixed -benchtime times -count runs
Variance±5 percent worst case on microsecond loops, per the author's measurement±1 to 2 percent worst case
Heap trackingNone: B/op and allocs/op are never recordedYes: B/op and allocs/op from CountingAlloc
ReportingHTML report with plots, named baselines, regression detectionbenchfmt text files; statistics and tables come from benchstat
Cross-language compareNo: pairwise baseline folders onlyYes: one benchstat table across Go and Rust with -col /lang
Dependencies42 transitive crates even with default features off; about 19 seconds on a cold buildZero dependencies
ChannelStable RustStable Rust; only the libtest converter needs nightly output

Who Should Use benchfmt.rs, and Who Should Not?

benchfmt.rs fits a specific developer. You benchmark Rust code, you want to compare it against Go code or against an earlier commit with real statistics, and you care about allocations as much as time. If that is you, the converter gives you benchstat tables today without rewriting anything, and the Suite gives you the full feature set when you are ready.

The decision table below is the short version of this section.

Platform and infrastructure engineers are the natural audience. If you maintain a library with both a Go and a Rust implementation, or you are porting a Go service to Rust and need to prove the port is not slower, this is the tool for the job. The same evidence-first habit applies elsewhere in platform work: if you run Kubernetes, our guide to how to use kube-upgrade-check shows how to find what breaks before an upgrade, with the same before-and-after discipline.

And if you use an AI coding agent to write your benchmark harnesses, our guide to how to use Claude Code covers the workflow, while our explainer on what One Code is shows the same "bring one workflow to another tool" pattern that benchfmt.rs applies to benchmarking.

Who should not use it? Anyone who needs a stable, supported tool today. The crate is three days old, has no release, no version number, and 24 stars.

No independent review exists, the Hacker News reception is small and mixed, and one commenter found the pitch unclear next to hyperfine and Criterion. If your team needs a documented, battle-tested benchmarking story, Criterion is the answer, and the converter lets you keep it while still getting benchstat tables. Revisit the Suite when the crate has a release, a changelog, and a few months of real-world use.

You areVerdictWhy
Comparing Go and Rust implementations of the same codeUse the SuiteOne benchstat table with -col /lang, plus heap columns on both sides
Running Criterion already and want A/B tablesUse the converterOne command, no code changes, approximate p-values but real tables
Chasing an allocation regressionUse the SuiteB/op and allocs/op are the only way to see it; Criterion cannot measure it
Writing a new Rust benchmark suite from scratchTry the Suite, with careZero dependencies and tight variance, but the crate is three days old
Needing a stable, supported tool todayStay on CriterionIt is documented, dual-licensed, and battle-tested; use the converter for tables
Benchmarking multi-threaded codeUse the Suite with AllocTrackerThe default counter is thread-local; pass a process-wide tracker with Suite::with_alloc

What Are the Limits and Early-Stage Caveats?

Every limit below comes from the crate's own README or the author's post, and none of them is a reason to dismiss the crate. They are the reasons to use it with open eyes. The first limit is the simplest: there is no release, no tag, and no version number, so installation is from git only, and the API can change without notice.

Pin a commit if you adopt it, and check the repository before you upgrade.

The Suite is not a full port of Go's testing.B. Its -bench flag strips the ^, $, and Benchmark prefix and matches with str::contains, not full regular expressions. Its -cpu flag only sets the -N suffix on benchmark names; it does no thread sweep and no scheduler tuning.

There is no StopTimer or StartTimer yet, and no custom ReportMetric units yet. Benchmarks register flat names, with no nested b.Run sub-closures. If you depend on any of those, the Suite is not ready for you.

The converter's epochs are approximate, as the previous sections explained, and the libtest converter gives one summary line per benchmark per run, so it cannot produce p-values from a single run. The heap counter is thread-local by default. The Hacker News reception, 30 points and 7 comments as of the November 5 snapshot, is small and mixed, and no independent explainer, how-to, or review of benchfmt.rs was published between November 3 and November 5.

The only substantive sources are the author's own post and repository.

None of this is unusual for a three-day-old crate, and the author is the first to say so. The converter exists precisely because a new benchmarking runtime should not be trusted from the start. Use it to get benchstat tables from the suite you already have, watch the repository for a first release, and adopt the Suite when it has earned that trust.

This article promises no CodexFuse benchfmt.rs tool; it is a guide to the author's crate, nothing more.

Sources

Bartek Plotka, "benchfmt.rs: Bringing Go's Benchmarking and benchstat Workflow to Rust", bwplotka.dev, published November 3, 2026. All benchmark numbers, machine details, and pprof findings in this article come from this post, including the varint and Prometheus Remote Write 2.0 workloads, the 24-core Intel Xeon 2.20 GHz workstation, Go 1.26.8, and Rust 1.99.1.

benchfmt.rs README, github.com/bwplotka/benchfmt.rs, fetched November 5, 2026. Tagline, features, CLI and Suite usage, pprof setup, scope limits compared with Go's testing.B, and the Apache-2.0 license. GitHub REST API repository metadata, fetched November 5, 2026: created November 2, 2026, 24 stargazers, 0 forks, 2 open issues, last push November 5, 2026.

GitHub REST API releases and tags, fetched November 5, 2026: both empty, so no version number exists.

Go benchstat documentation, pkg.go.dev/golang.org/x/perf/cmd/benchstat, fetched November 5, 2026. Median, 95 percent confidence interval, Mann-Whitney U test, p-values, geometric mean, default alpha 0.05, and the -filter, -table, -row, -col, and -ignore flags. Go benchfmt package documentation, pkg.go.dev/golang.org/x/perf/benchfmt, fetched November 5, 2026.

Go benchmark data format proposal 14313, golang.org/design/14313-benchmark-format, by Russ Cox and Austin Clements, last updated February 2016.

Criterion.rs book, bheisler.github.io/criterion.rs/book/, fetched November 5, 2026. "Criterion.rs is a statistics-driven micro-benchmarking tool. It is a Rust port of Haskell's Criterion library." The Cargo Book, cargo-bench command page, doc.rust-lang.org: the #[bench] attribute is unstable and only available on the nightly channel, and the Cargo Book points stable users at crates such as Criterion.

Hacker News Algolia API, hn.algolia.com, fetched November 5, 2026: one story, "Benchfmt.rs: Bringing Go's benchmarking and benchstat workflow to Rust", posted by witcher on November 4, 2026, 30 points, 7 comments. All figures in this article are the author's own or from the named sources above, and none was independently verified.

Frequently Asked Questions

Is benchfmt.rs the same thing as Go's benchfmt?

No. Go's benchfmt is the benchmark output format and the golang.org/x/perf/benchfmt package that reads and writes it, defined by proposal 14313 in 2016. benchfmt.rs is a new Rust crate that emits that same format from Rust code, so benchstat can analyze Rust results. They share a name and a format, and the crate's README states it brings Go's benchfmt to Rust.

A few unrelated older Go packages also use the benchfmt name, and none of them is this crate.

Does benchfmt.rs replace Criterion?

No, and it is built to work with Criterion, not against it. The benchfmt CLI converts existing Criterion output into benchfmt format, so you keep your Criterion suite untouched and still get benchstat tables. The Suite runner is an alternative for new benchmarks, with heap tracking and tighter variance, but Criterion's HTML reports, plots, and named baselines are features benchfmt.rs does not reproduce.

Most users should start with the converter and adopt the Suite only if the tables prove useful.

Does benchfmt.rs work on stable Rust?

Yes, for the parts most people need. The Suite runner and the Criterion converter both work on stable Rust. Only the libtest converter needs nightly output, because Rust's built-in #[bench] attribute is unstable and only available on the nightly channel behind #![feature(test)].

If you benchmark on stable Rust, which most projects do, the Suite and the converter both work for you.

Is benchfmt.rs production-ready?

Not yet, by any reasonable standard. The repository was created on November 2, 2026, it has no releases, tags, or version number, it has 24 stars and 0 forks, and no independent review exists. The author himself ships the converter so you do not have to trust the new runtime from the start.

Treat it as an experiment to watch: use the converter for tables today, pin a commit if you adopt the Suite, and wait for a first release before depending on it.

Can I compare Go and Rust benchmarks in one table?

Yes, and that is the whole point of the crate. Write your benchmark names with /key=value labels, such as /lang=go and /lang=rust, emit both sides in benchfmt format, and run benchstat -col /lang results/go.txt results/rust.txt. benchstat prints one table with Go and Rust as columns, including medians, confidence intervals, p-values, and a geometric mean. The author's own tables do exactly this, with 6 epochs per benchmark, and the same trick works for any label, such as -col /samples for different input sizes.

Featured on Twelve Tools Hunt for Tools Featured on LaunchBuff Launch Llama Tools Launch your MCP Top Web Development Companies in the US - OnToplist.com Designer Listings Listed on SeeWhatNewAI Featured on SubmitMySaas