perf(bench-ratchet): seed the baseline from a window, reduced in ratio space - #684
perf(bench-ratchet): seed the baseline from a window, reduced in ratio space#684mparrett wants to merge 1 commit into
Conversation
ae6ccee to
5601f15
Compare
|
#564 landed on This PR still lists the pre-squash #564 commits ( No rush and no action needed from my side — flagging it since the blocker you were waiting on is cleared. |
…o space seed-baseline took the newest snapshot per machine key. One snapshot is one CI run, and one CI run is one sample: seeded from the newest versus a median of five on the same corpus, 22.6% of the 758 (tier, benchmark) floors differ by more than the 5% regression budget and 11.3% by more than 10%. Some of that is real code movement across the window and some is sampling, but either way a fifth of the gate's thresholds were set by a single observation. Take a window (-seed-window, default 5) and median it. Median rather than min because `update` already takes a min over history when it ratchets, and a second minimum would stack into a floor no clean run can reach. Reduce in ratio space, deriving ns_per_op back from the window's anchor: raw ns_per_op carries host speed and ratio_to_anchor does not. Over the 24 most recent amd64 snapshots, anchor deviation from the tier median spans -22.4%..+3.1% while ratio_to_anchor holds to a median 0.03%, worst 1.75%. Gate on that coherence rather than on anchor drift. Gating on the anchor was the obvious design and the corpus refutes it: a588a69 sits 22.4% off its window's anchor, is uniformly 22.4% fast in raw ns/op across all 162 of its benchmarks, and agrees with its window on every ratio to within 0.1% — the host was fast and the anchor divided it back out. That check would have discarded three good captures, two of them snapshots this baseline is seeded from. What needs rejecting is the mixed capture, where the ratios move together while the raw numbers look ordinary. Also, from reading the seam rather than the samples: - Enforce the arch filter on file CONTENT, not just the filename, and warn when the two disagree. That divergence put an EPYC profile under an Intel key once already, and the log printed the filename. - Parse snapshot names with an anchored regexp. A positional split on "-" yields a plausible-looking wrong SHA, and the machine slug is full of dashes. - Skip a benchmark present in under half the window instead of seeding it from one observation; check reports it as NEW, which is honest. - Report b.N movement across the window. b.N is an output of the timing loop, so it moves when per-op cost moves, and ns/op is N-dependent wherever iterations share state. - Mark every matching exclusion prefix, not the first. BenchmarkClojureTestSuite is a prefix of BenchmarkClojureTestSuiteCompileAndRun, so the longer entry was reported as stale on every run while it was in fact filtering. Provenance: every number above is a re-derivation over snapshots already committed on perf-data — reproduce with -seed-window 1 against -seed-window 5 and diff ratio_to_anchor. The tight-core-plus-slow-tail characterisation that motivated windowing in the first place (roughly 2 launches in 20 landing 15-29% high, byte-identical binaries landing 3.7% apart) is from a dedicated fixed-performance EC2 box in a sibling project, on different benchmark shapes; whether that tail generalises here is what a null control would settle.
5601f15 to
335b170
Compare
|
Thanks for the rebase. The argument for ratio-space reduction convinced me, and the The open question: this lands the method but not the result. One nit: M3 preservation now keys only on I ran the unit tests, not a seed against the real perf-data timeline, so the corpus figures |
Follow-up to #564, which I said I'd open. #564 has landed and this is now rebased onto it: one commit, five files.
baseline.jsonis no longer in the diff, since main already carries the regenerated file from #564.seed-baselinecurrently takes the newest snapshot per machine key. This takes a window of five and medians it, reduces in ratio space rather than rawns/op, and gates on whether a snapshot agrees with its window instead of on how far its anchor drifted.Correction to my #564 review
I said there to skip a candidate whose anchor is an outlier against its own tier. I built that, ran it against perf-data, and the corpus refutes it.
Snapshot
a588a69d2759(EPYC 9V74) sits 22.4% off its window's anchor. It is also uniformly 22.4% fast in rawns/opacross all 162 of its benchmarks, and agrees with its window on everyratio_to_anchorto within 0.1%. The host was fast that day and the anchor divided it back out, which is what the anchor is for. Anchor-deviation gating would have discarded it and two more like it, two of which are snapshots the current baseline is seeded from.Over the 24 most recent amd64 snapshots:
ratio_to_anchoroffset from tier medianSo the gate is on ratio coherence: a snapshot is rejected when its ratios sit more than
-seed-coherence-tolerance(default 5%) off the rest of the window. That catches the case that damages a baseline — the mixed capture, where the anchor caught the slow tail and the benchmarks did not, so every ratio is uniformly wrong while the raw numbers look ordinary. Nothing in the current corpus trips it, which is the expected result and not evidence the check is idle.Method
A re-derivation over snapshots already committed on
perf-data— no new measurement runs, reproducible with-seed-window 1against-seed-window 5and a diff ofratio_to_anchor. Provenance for the tail figures behind the windowing argument is in the commit message.Why a window
One snapshot is one CI run, and one CI run is one sample. Seeding from the newest versus a median of five, same corpus, same day:
Part of that spread is real code movement across the window and part is sampling; I can't separate them without a null control. Either way, seeding from one snapshot sets a fifth of the gate's thresholds from a single observation of it.
Median rather than min:
updatealready takes a min over history when it ratchets, and seeding with a second minimum stacks two of them into a floor no clean run reaches.Why ratio space
Raw
ns_per_opcarries host speed;ratio_to_anchordoes not — see the table above. Reducing the quantity that carries host speed and then dividing imports that speed into the stored floor. The two approaches agree whenever a window comes from one host and diverge exactly when it does not, which a shared runner pool guarantees.ns_per_opis derived back from the reduced ratio and the window's anchor, soratio_to_anchor == ns_per_op / anchor.ns_per_opholds by construction.Additional fixes
-yields a plausible-looking wrong SHA, and the machine slug is full of dashes. Unparseable names are skipped and reported — 120 of them in the current corpus, all predating the machine-slug naming.checkreports it as NEW, which is honest.b.Nmovement across the window is reported.b.Nis an output of the timing loop, so it moves when per-op cost moves, andns/opis N-dependent wherever iterations share state. Reported, not acted on: excluding on it would shrink the gate silently.BenchmarkClojureTestSuiteis a prefix ofBenchmarkClojureTestSuiteCompileAndRun, so the longer entry was reported as stale on every run while it was in fact filtering.Verification
go test ./cmd/bench-ratchet— 21/21, 11 of them new, including the negative result above asTestSeedBaselineKeepsUniformlyFastSnapshotand the mixed capture it does reject.perf-datatimeline: 5 amd64 tiers seeded, 134–156 benchmarks each, no snapshot rejected, M3 preserved.-seed-window 1reproduces the current behaviour, which is how the comparison table above was produced.Not addressed here
The 5% budget still has no measured floor under it. Nothing in this PR tells you whether 5% is above or below the gap between two builds that cannot differ on these tiers — a comment-only-edit control run through the existing dispatch would. Worth doing before the budget is tightened.