Skip to content

[ci-test] #22 reducer scoping — validate perf-pr on fork - #17

Closed
mparrett wants to merge 5 commits into
mainfrom
perf/bench-ratchet-min
Closed

[ci-test] #22 reducer scoping — validate perf-pr on fork#17
mparrett wants to merge 5 commits into
mainfrom
perf/bench-ratchet-min

Conversation

@mparrett

Copy link
Copy Markdown
Owner

Throwaway fork-internal PR to exercise perf-pr.yml with the new -reducer min flag on real CI. Not for merge — mirrors nooga#22. Closeable once the perf-pr A/B run goes green.

mparrett and others added 5 commits July 11, 2026 23:35
aggregateFromFile averaged the N -count repetitions of each benchmark. Mean is
the wrong reducer for benchmark noise, which is one-directional: interference
(GC pauses, CPU migration, a co-scheduled runner tenant, thermal throttling)
only ever makes a run slower, never faster. A single slow sample drags the mean
up and manufactures a phantom regression — exactly what the small (<20ns)
BenchmarkGetOwn variants did on shared CI runners, flip-flopping 6/2/4
"regressions" across three otherwise-identical perf-pr runs on code paths the
PRs never touched.

Reduce by minimum instead: the fastest of N repetitions is the least
contaminated estimate of true cost. All raw samples are still retained in
Samples for provenance. alloc/bytes are deterministic per op (min == mean).

Local stability check, BenchmarkGetOwn/n=16/last, count=6 x3 captures:
  mean-of-6 spread across captures: 3.1%
  min-of-6  spread across captures: 0.8%   (~4x tighter; larger on noisier CI)

Adds cmd/bench-ratchet/main_test.go (first test for the package) pinning the
min reduction.

Note: perf-pr's base-vs-head A/B benefits immediately and safely (both halves
measured with the same reducer in one run). perf-timeline snapshots on the
perf-data branch were captured with mean, so the first snapshot after this
lands steps down once (min < mean) — a one-commit discontinuity on the
historical dashboard, not a regression. Do NOT put the `perf` label on this PR:
it would compare base's mean-reducer against head's min-reducer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n real samples

TestMinVsMeanFlipsRegressionVerdict feeds 15 real captured
BenchmarkGetOwn/n=16/last samples (memory-bound; a clean cluster plus a
contention-slowed upward tail) and the anchor through the actual aggregate +
compareAndReport path at perf-pr's 10% budget:

  min-reduced  ratio 9.536/1.172 = 8.14  -> 0 regressions (true cost)
  mean-reduced ratio 11.759/1.243 = 9.46 -> 1 regression (+16% phantom)

Same samples, opposite verdict — the phantom the min reducer removes, shown
deterministically and runnable with `go test`. Complements the existing
reduction unit test with the verdict-level effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… lower-envelope heuristic

Addresses review feedback on nooga#22 — interpretation and data compatibility, not code
correctness (implementation tests + CI were green).

Provenance / compatibility:
- perfdata.Baseline gains Reducer + SampleCount; buildCurrentBaseline stamps them ("min",
  N observed samples). A minimum is sample-count dependent, so this is the metadata needed
  to keep min-era and mean-era snapshots from being silently compared.
- schemaVersion 1 -> 2: mean-era (v1, no provenance) baselines are now rejected on read.
  `update` re-seeds on the mismatch and prints an explicit "intentional timeline
  discontinuity" note rather than ratcheting a min-era bar onto a mean-era one.
- compareAndReport warns on a same-version reducer/count mismatch (forward guard for the
  median-of-N reducer).

Reframe (min selects a lower envelope, it does not estimate "true cost"):
- aggregateFromFile doc reworded: pragmatic lower-envelope heuristic under an
  upward-contamination assumption, informational-only, never a gate; notes favorable
  samples exist and that a defensible gate is the median-of-N repeat A/B, not this reducer.
- Rename TestMinVsMeanFlipsRegressionVerdict -> TestReducerChoiceChangesRegressionVerdict.
  Its 15 real samples are a regime transition (median 12.37), so it demonstrates verdict
  SUPPRESSION under upward contamination, not proof-of-phantom.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…chet

Addresses the CHANGES_REQUESTED review's three concerns:

1. Reducer compounding (P1). aggregateFromFile was unconditionally min, but it
   feeds both the throwaway per-run A/B *and* the persistent ratchet — and
   ratchetMerge already takes min over all history, so min-of-N input compounds
   into a floor a single lucky sample can set unclearably low. Add a -reducer
   flag: "mean" is the default (safe for the durable baseline), "min" is opt-in
   for the informational path. perf-pr.yml passes -reducer min on both A/B halves
   (base update + head check, against a RUNNER_TEMP baseline — no persistence).
   The reducer is recorded on the Baseline, so a min-era and mean-era point are
   never silently diffed.

2. N=3 evidence. The verdict-flip proof uses N=15; production ships -count 3.
   Add TestMinReducerRobustAtProductionCount3: at the shipped count, min-of-3 is
   invariant to a single upward-contaminated sample (stays pinned to the clean
   floor) while mean-of-3 moves — min is stable, not outlier-chasing.

3. baseline.json migration. The committed docs/perf/baseline.json was version 1,
   so a post-merge `check` would hard-fail on the version gate. It was captured at
   -count 1 (one sample per benchmark), where min == mean, so this is a faithful
   metadata-only stamp — version 2, reducer "mean", sample_count 1 — with the
   numbers untouched (no re-benchmark on a foreign CPU).

go build/vet/gofmt clean; bench-ratchet tests pass; actionlint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rge-bases

The A/B builds bench-ratchet from the merge-base commit, which can predate the
-reducer flag; passing it unconditionally crashes that half ("flag provided but
not defined: -reducer") for any PR branched before this change. Detect support on
the base binary and reuse the decision for the head half via GITHUB_ENV, so both
halves run the SAME reducer: min when supported, and a graceful fall-back to the
default (mean) when the base is too old — like-for-like either way, since this is
the informational, non-gating report. Caught by a fork self-A/B dispatch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Perf — base vs head, same runner

Base 6c83bf9d92b2 vs head d81f41fe7f55. Anchor-normalized; informational, not a gate.

Run details →

@mparrett

Copy link
Copy Markdown
Owner Author

Done — perf-pr A/B validated green on the fork with the feature-detect fix (nooga#22). Throwaway vehicle, nothing to merge.

@mparrett mparrett closed this Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant