Skip to main content
Version: 1.5

Benchmarks

What the code costs, as opposed to what it does and what the suite reached.

Measured on 2026-08-31 at commit 8ab940b289ba, by the release that published this site.

This release measured 32 benchmarks across 3 modules, on linux/amd64, AMD EPYC 7763 64-Core Processor.

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

BenchmarkTime/opBytes/opAllocs/opIterations
BenchmarkNormalizeRewrite794.5 ns240 B11,297,449
BenchmarkParseDirectives14.2 µs2.5 KiB16134,010
BenchmarkParseInvalid5.9 µs1.3 KiB14260,913
BenchmarkParseSimple5.6 µs1.2 KiB5297,630
BenchmarkParseSubject3.8 µs832 B2677,516

pkg/config

linux/amd64, AMD EPYC 7763 64-Core Processor

BenchmarkTime/opBytes/opAllocs/opIterations
BenchmarkApplyEdits/json1.6 ms13.4 KiB118772
BenchmarkApplyEdits/yaml2.3 ms404.1 KiB1,891541
BenchmarkDecode/full164.9 µs28.4 KiB5148,535
BenchmarkFold/lower13.59 ns0 B083,989,353
BenchmarkFold/mixed73.90 ns8 B118,577,078
BenchmarkFold/unicode242.1 ns16 B15,453,582
BenchmarkReadTree/json/large12.8 ms413.0 KiB5,635100
BenchmarkReadTree/json/medium326.6 µs32.1 KiB5827,861
BenchmarkReadTree/json/small25.9 µs3.6 KiB7242,118
BenchmarkReadTree/toml/large11.3 ms681.7 KiB8,624126
BenchmarkReadTree/toml/medium427.5 µs54.7 KiB8823,463
BenchmarkReadTree/yaml/medium1.0 ms106.9 KiB1,9991,431
BenchmarkReadTree/yaml/small109.3 µs16.8 KiB24411,781
BenchmarkRefDepth/140.1 µs4.9 KiB9345,984
BenchmarkRefDepth/32304.7 µs50.9 KiB6926,402
BenchmarkRefDepth/8115.0 µs15.1 KiB23210,000
BenchmarkRefWidth/223.6 µs3.6 KiB5867,412
BenchmarkResolve/16302.5 µs17.8 KiB1743,759
BenchmarkResolve/4106.4 µs7.0 KiB7610,000
BenchmarkSettings/dotted263.6 µs76.9 KiB5057,558
BenchmarkSettings/empty-objects83.2 µs6.6 KiB515,628
BenchmarkSettings/flat58.8 µs6.6 KiB529,368
BenchmarkSettings/nested77.4 µs11.7 KiB6517,619

services/dispat

linux/amd64, AMD EPYC 7763 64-Core Processor

BenchmarkTime/opBytes/opAllocs/opIterations
BenchmarkComputeGroup/fixed3.6 ms560.4 KiB3,416316
BenchmarkComputeGroup/fixedMajor3.8 ms562.7 KiB3,414295
BenchmarkComputeGroup/fixedMajorMinor3.3 ms562.7 KiB3,413366
BenchmarkComputeGroup/fixedSparse2.7 ms459.3 KiB2,198397

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

TargetPackageCorpus entries
FuzzNormalizepkg/ccme114
FuzzParsepkg/ccme116
FuzzParseSubjectpkg/ccme114
FuzzParserspkg/config9
FuzzRefTargetspkg/config8
FuzzSettingspkg/config5
FuzzGradleLinepkg/scanner16
FuzzPBXSettingpkg/scanner9
FuzzParserspkg/scanner39
FuzzPep508Deppkg/scanner8
FuzzPodDeclarationpkg/scanner12
FuzzPodspecStatementpkg/scanner10
FuzzLinkpkg/writer12
FuzzReplaceBytespkg/writer8
FuzzRewriteAndroidManifestpkg/writer7
FuzzRewriteCargopkg/writer8
FuzzRewriteComposepkg/writer9
FuzzRewriteComposerpkg/writer6
FuzzRewriteCsprojpkg/writer6
FuzzRewriteDockerfilepkg/writer9
FuzzRewriteGemfilepkg/writer6
FuzzRewriteGodotCfgpkg/writer10
FuzzRewriteGradleBuildpkg/writer8
FuzzRewriteGradleCatalogpkg/writer8
FuzzRewriteMavenpkg/writer7
FuzzRewriteNpmpkg/writer8
FuzzRewriteNuGetListspkg/writer6
FuzzRewriteNuspecpkg/writer6
FuzzRewritePlistpkg/writer8
FuzzRewritePodfilepkg/writer8
FuzzRewritePubspecpkg/writer6
FuzzRewritePyprojectpkg/writer9
FuzzRewriteUPluginpkg/writer8
FuzzRewriteUnityProjectSettingspkg/writer8
FuzzRewriteXcodeProjpkg/writer7

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.