Benchmarks
What the code costs, as opposed to what it does and what the suite reached.
Measured on 2026-08-31 at commit 481d94ff066a, by the release that published this site.
This release measured 25 benchmarks across 3 modules, on linux/amd64, AMD EPYC 7763 64-Core Processor and linux/amd64, INTEL(R) XEON(R) PLATINUM 8573C.
Every number on this page is measured rather than written. A benchmark pass runs go test -run '^$' -bench . -benchmem
per module, in a stage of its own, and the release folds the stream into the same report the coverage and test-results
pages read. Nothing here is a figure someone typed into a document, which matters more for a performance claim than for
almost any other kind: a benchmark number written by hand is a number about a machine that no longer exists.
-run '^$' is not optional. Without it every test runs again alongside the benchmarks, and the timings become numbers
about a machine that was busy doing something else. -benchtime is left at Go's own default, because a measurement
taken over fewer iterations than the tool chose is a measurement nobody can compare.
By module
pkg/ccme
linux/amd64, AMD EPYC 7763 64-Core Processor
| Benchmark | Time/op | Bytes/op | Allocs/op | Iterations |
|---|---|---|---|---|
BenchmarkParseCRLF | 5.1 µs | 1.2 KiB | 5 | 268,934 |
BenchmarkParseMultiUnit | 34.5 µs | 3.8 KiB | 12 | 34,166 |
BenchmarkParseSimple | 6.6 µs | 1.2 KiB | 5 | 279,295 |
BenchmarkParseSubject | 4.8 µs | 832 B | 2 | 800,246 |
pkg/config
linux/amd64, AMD EPYC 7763 64-Core Processor
| Benchmark | Time/op | Bytes/op | Allocs/op | Iterations |
|---|---|---|---|---|
BenchmarkApplyEdits/json | 3.0 ms | 13.5 KiB | 118 | 417 |
BenchmarkDecode/fold-hit | 214.6 µs | 28.5 KiB | 540 | 10,000 |
BenchmarkDecode/full | 242.3 µs | 28.4 KiB | 514 | 8,815 |
BenchmarkDecode/small | 5.0 µs | 1.0 KiB | 22 | 253,243 |
BenchmarkFold/mixed | 85.43 ns | 8 B | 1 | 12,792,060 |
BenchmarkFold/unicode | 248.2 ns | 16 B | 1 | 4,715,982 |
BenchmarkOverrides | 78.7 µs | 12.2 KiB | 72 | 15,931 |
BenchmarkReadTree/json/large | 3.3 ms | 413.0 KiB | 5,635 | 516 |
BenchmarkReadTree/json/medium | 211.0 µs | 32.1 KiB | 582 | 7,767 |
BenchmarkReadTree/json/small | 36.1 µs | 3.6 KiB | 72 | 59,445 |
BenchmarkReadTree/toml/large | 9.3 ms | 681.7 KiB | 8,624 | 134 |
BenchmarkReadTree/yaml/large | 14.9 ms | 1.1 MiB | 19,626 | 68 |
BenchmarkReadTree/yaml/small | 109.5 µs | 16.8 KiB | 244 | 10,000 |
BenchmarkRefWidth/2 | 22.5 µs | 3.6 KiB | 58 | 60,968 |
BenchmarkRefWidth/32 | 308.3 µs | 41.8 KiB | 635 | 5,562 |
BenchmarkSettings/empty-objects | 61.7 µs | 6.6 KiB | 5 | 19,348 |
services/dispat
linux/amd64, INTEL(R) XEON(R) PLATINUM 8573C
| Benchmark | Time/op | Bytes/op | Allocs/op | Iterations |
|---|---|---|---|---|
BenchmarkComputeGroup/fixed | 2.5 ms | 560.1 KiB | 3,415 | 482 |
BenchmarkComputeGroup/fixedMajor | 1.3 ms | 562.3 KiB | 3,410 | 817 |
BenchmarkComputeGroup/fixedMajorMinor | 2.7 ms | 562.6 KiB | 3,412 | 381 |
BenchmarkComputeGroup/fixedMajorSparse | 845.1 µs | 458.6 KiB | 2,193 | 1,339 |
BenchmarkComputeGroup/fixedSparse | 2.0 ms | 459.2 KiB | 2,197 | 691 |
A row is one benchmark function, or one case of a table-driven one, named as its source names it. The iteration count is a column rather than a footnote because it is what says how much to trust the row beside it: a benchmark the tool ran ten times and one it ran ten million times are not the same kind of number. The machine is stated per module for the same reason — a nanosecond figure without the CPU that produced it is a number nobody can compare anything to, and a release runner is not a workstation.
Allocations are the figure to read first. A time depends on the machine, the toolchain and whatever else was running; an allocation count is a property of the code, and it moves only when someone changes what the code does. That is why several of these packages pin their allocation counts as tests — a budget with one allocation of headroom, so a toolchain change does not fail a build for a rounding difference while a reintroduced copy fails it immediately.
Fuzz targets
| Target | Package | Corpus entries |
|---|---|---|
FuzzNormalize | pkg/ccme | 114 |
FuzzParse | pkg/ccme | 116 |
FuzzParseSubject | pkg/ccme | 114 |
FuzzParsers | pkg/config | 9 |
FuzzRefTargets | pkg/config | 8 |
FuzzSettings | pkg/config | 5 |
FuzzGradleLine | pkg/scanner | 16 |
FuzzPBXSetting | pkg/scanner | 9 |
FuzzParsers | pkg/scanner | 39 |
FuzzPep508Dep | pkg/scanner | 8 |
FuzzPodDeclaration | pkg/scanner | 12 |
FuzzPodspecStatement | pkg/scanner | 10 |
FuzzLink | pkg/writer | 12 |
FuzzReplaceBytes | pkg/writer | 8 |
FuzzRewriteAndroidManifest | pkg/writer | 7 |
FuzzRewriteCargo | pkg/writer | 8 |
FuzzRewriteCompose | pkg/writer | 9 |
FuzzRewriteComposer | pkg/writer | 6 |
FuzzRewriteCsproj | pkg/writer | 6 |
FuzzRewriteDockerfile | pkg/writer | 9 |
FuzzRewriteGemfile | pkg/writer | 6 |
FuzzRewriteGodotCfg | pkg/writer | 10 |
FuzzRewriteGradleBuild | pkg/writer | 8 |
FuzzRewriteGradleCatalog | pkg/writer | 8 |
FuzzRewriteMaven | pkg/writer | 7 |
FuzzRewriteNpm | pkg/writer | 8 |
FuzzRewriteNuGetLists | pkg/writer | 6 |
FuzzRewriteNuspec | pkg/writer | 6 |
FuzzRewritePlist | pkg/writer | 8 |
FuzzRewritePodfile | pkg/writer | 8 |
FuzzRewritePubspec | pkg/writer | 6 |
FuzzRewritePyproject | pkg/writer | 9 |
FuzzRewriteUPlugin | pkg/writer | 8 |
FuzzRewriteUnityProjectSettings | pkg/writer | 8 |
FuzzRewriteXcodeProj | pkg/writer | 7 |
A fuzz target under a plain go test runs its corpus rather than fuzzing: the seeds the test declares with f.Add,
plus whatever testdata/fuzz holds for it — the inputs earlier fuzzing sessions found interesting, kept as regression
cases. The corpus column is what every suite run exercises, and it is the honest half of the number. It is not a claim
that a fuzzing session ran here, because none did.
Fuzzing itself is a thing somebody does on purpose, for as long as they have:
go test ./... -run '^$' -fuzz FuzzParse -fuzztime 10m
A crash it finds is written into testdata/fuzz as a new corpus entry, committed, and from then on every run of the
ordinary suite replays it. That is why the corpus grows and why the count above is worth reading: it is the number of
inputs the suite is holding the parsers to, not the number of inputs anyone has tried.
Reproducing
The whole measurement is one command per module from the monorepo root, and it is the one the release runs:
go run github.com/yohimik/dispat/tools/testreport bench config -- ./... -run '^$' -bench . -benchmem
go run github.com/yohimik/dispat/tools/testreport build # writes packages/docs/data/report.json, read by this page
The stream lands in coverage/benchlog/<module>.json, beside the go test -json logs the suites leave in
coverage/testlog/. A module measured by a run replaces whatever it had; a module the run did not measure keeps it, so
a release that rebuilt only part of the monorepo does not blank a page. Rebuilding this site with
packages/docs/data/report.json in place is what puts the figures back; without one, the prose stays and the tables
are left out.
What a benchmark here is for
These are regression gates, not marketing. A benchmark exists in this repository when there is a specific mistake it would catch: a second deep copy of a parsed document, a flat intermediate map materialised only to be rebuilt, a per-element allocation in a loop that could have hoisted it. The corresponding allocation budget is what turns the benchmark into a gate — the benchmark says what it costs, and the budget fails the build when the cost changes for a reason nobody intended.
What none of them are is a comparison against another library. A benchmark measuring two things measures the harness between them, and this repository has no honest way to run somebody else's code on the same terms.