Verdict: PASS. Build overhead is +0.4 % against a +8 % budget, measured on a like-for-like comparison of the reference sample.
Recorded: 2026-07-30 · WP-14
ADR-0002 moved orchestration to compile time and accepted generator complexity as permanent legacy risk (R1). Its revisit-when clause names a threshold:
Revisit when: generator maintenance cost exceeds its benefit (measured as > 3 generator defects per delivery phase, or build overhead > 8 % sustained).
That clause could never have fired, because nothing measured build overhead. The budget was declared in 14-Performance §1 — "Build overhead vs identical non-FlowX code, +8 %" — and left unmeasured through P0.
Two builds of the reference sample producing the same final compilation. The only difference is whether the generator ran:
| Arm | How |
|---|---|
| with | the analyzer referenced, generator running |
| without | the analyzer dropped, the generated .g.cs compiled as ordinary source |
Ecommerce.csproj carries an MSBuild condition for the second arm, so the sample really
does build both ways. Dependencies are built once and reused, because they are identical
in both arms and rebuilding them would dilute the very difference being measured. Arms
alternate within a round, so a machine that slows down partway penalises both equally.
./scripts/measure-build-overhead.sh 15with the generator 2613 ms (min 2494, max 2861)
without the generator 2603 ms (min 2392, max 2697)
B12 build overhead +0.4 % budget +8 %
within-arm spread 14.0 % of the median
Medians over 15 rounds, not means: one scheduling hiccup on shared hardware moves a mean and leaves a median alone.
PASS. And the reason is legible: the generator costs ~3 ms (§3) against a project build of ~2.6 s. Roughly a tenth of a percent — far below both the budget and the measurement's own noise.
The two arms' ranges overlap, and the script says so rather than printing a number and stopping. A within-arm spread of 14 % against a difference of 0.4 % means the measurement cannot distinguish +0.4 % from −0.4 %. It is not evidence that the overhead is exactly 0.4 %; it is evidence that the overhead is nowhere near 8 %, which is the question the budget asks.
Sharpening it further would need dedicated hardware, and there is no decision waiting on the difference between 0.4 % and 2 %.
CompilerBenchmarks drives Roslyn directly rather than timing dotnet build, because an
MSBuild wall-clock is dominated by restore, dependency resolution and file IO, none of
which the generator touches.
The compilation is one file: four capabilities and a flow with a compensation, an emit and a return.
BenchmarkDotNet v0.15.2 · Linux Ubuntu 24.04.4 LTS
Intel Xeon Processor 2.10GHz, 1 CPU, 4 logical and 4 physical cores
.NET SDK 10.0.110 · IterationCount=30 WarmupCount=10 DOTNET_TieredPGO=0
| Measurement | Mean | Allocated |
|---|---|---|
| Compile without the generator | 4.04 ms | 678 KB |
| Run the generator only | 2.86 ms | 589 KB |
| Compile with the generator, output bound | 11.17 ms | 1 509 KB |
Two runs of the same commit put "compile without the generator" at 3.26 ms and 4.04 ms — a 24 % swing. This is the same shared container the P0 report documents, and there the 29× margin absorbed it. Here there is no margin to absorb anything, which is a second reason its ratio is not a verdict.
Three problems with reading a verdict off those numbers, none fixable by re-running.
The control compiles a file containing no plan and no dispatcher. The third row compiles that file plus the generated plan and dispatcher — roughly twice the code. Most of the 2.8× is binding source the control does not contain.
An application written without FlowX would have hand-written that dispatcher and paid to bind it. So the difference is not overhead; it is partly work moved, not work added. Reporting that ratio as "build overhead" would overstate the cost, and would be the same kind of claim this repository has spent WP-10 through WP-13 removing.
"Run the generator only" measures less than the control. That is not the generator
being free. RunGeneratorsAndUpdateCompilation binds only what the generator
asks for, so it does part of the control's work and skips the rest. The two numbers
overlap by an unknown amount, and subtracting them means nothing.
The generator's cost scales with the number of flows; a compilation's cost scales with the number of files. A one-file project maximises the generator's share, so that measurement is biased in a known direction rather than merely imprecise.
CompilerBenchmarks stays, and stays in the committed baseline, for what it can do:
price the generator in isolation. ~2.9 ms per compilation containing one flow is the
number §2's verdict rests on, and a change that made it 30 ms would show as a regression
against a committed figure long before it moved a whole-project build enough to notice.
The two measurements answer different questions, and the mistake was reporting the first one's ratio as though it answered the second's.
The verdict:
./scripts/measure-build-overhead.sh 15The generator's isolated cost:
dotnet run -c Release --project tests/FlowX.Benchmarks -- \
--filter '*CompilerBenchmarks*' --warmupCount 10 --iterationCount 30See also: ADR-0002 · P0 kill criterion · Benchmark harness · Plan