Skip to content

perf: budget the scenarios added in #177 and #180, and compare the fastest processes of each build - #194

Merged
unadlib merged 7 commits into
mainfrom
perf/scenario-budgets
Oct 8, 2026
Merged

unadlib merged 7 commits into
mainfrom
perf/scenario-budgets

Conversation

@unadlib

@unadlib unadlib commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Part of #168.

The CI performance budgets covered 8 of the 93 scenarios. The 26 that #177 and #180 added (Map and Set values, object records, class instances, the deep path, push and insert, patch application, returned values, and the current() searches) were validated in CI but had no budget, so their regressions went undetected. This PR budgets all 26, after measuring the noise between identical builds on GitHub runners. The measurements also showed why the existing gate failed on identical builds, as on #193, so the timing comparison changes as well.

  1. Timing budgets compare the fastest process of each build. About one process in six on GitHub runners ran the array moves 20–38% slower from start to end, base and candidate builds alike. The median of five paired ratios therefore failed whenever three or four slow processes fell to the candidate: 4 of the 113 runs uploaded from September 30 to October 8, 2026 failed this way, among them the first run of docs: migrate the website from Docusaurus to Fumadocs #193 between byte-identical builds (1.30–1.32 for array moves with patches). Compared by their fastest processes, all 112 runs that the gate can evaluate pass, at most 1.08 in those four. On 60 runs between identical builds, a simulated candidate slowdown of 1.35x exceeds the budget in 98% of the cells (96% with the medians) and one of 1.5x in all of them (99%); without a slowdown the medians flagged 0.15% of the cells, the fastest processes none. Memory keeps the medians of paired values.
  2. Budget groups run in jobs of their own. budgets.json (schema version 2) holds four groups: core, the eight existing scenarios and two memory cases, unchanged; collections; objects; and apply-return-search. node perf-testing/ci.mjs --group NAME runs one group, all groups in turn without it, and the workflow runs one job per group in 6–8 minutes, where the 98 new timing cells would have made one job take about 17. The gate derives each group's cells from the scenario registry, so patch application runs with patches off only, and rejects unknown groups or scenarios and scenarios budgeted in two groups; a unit test checks that the workflow matrix lists every group. Each job runs the budget tests instead of all benchmark tool tests, which Node CI already runs, so the occasional memory.test.mjs failure on GitHub does not now fail four jobs.
  3. Budgets for the 26 scenarios. All of them get the existing timing budget, 1.30x and 500 ns, in both freeze and patch modes. The heaviest eight also get memory budgets: map-update-10pct, set-update-10pct, object-update-10pct, class-wide-update, push-and-insert-reuse, apply-reverse, return-replace, and search-draft.
  4. Docs. The CI budget section of perf-testing/README.md describes the groups, the comparison, and the measured noise.

Noise floor

Before the new groups got budgets, a temporary workflow compared identical builds with --self-control on 30 GitHub runners, 10 per group: run 37817492628, whose branch has since been deleted. All 30 runs pass the final policy.

Group Timing cells Largest timing ratio Largest over all 252 splits of a run's 10 processes Largest allocation / retained-heap ratio
collections 36 1.032 1.057 (map-insert, auto-freeze) 1.029 / 1.008
objects 32 1.110 (class-update, 3.4 µs, below the 500 ns floor) 1.256 (object-delete, auto-freeze) 1.035 / 1.014
apply-return-search 30 1.051 1.097 (search-current-shifted, patches) 1.207 (1.3 KiB of 6.9 KiB, below the 8 KiB floor) / 1.053

No cell exceeded 1.30 in any split, and only object-delete with auto-freeze exceeded 1.20. Smaller cases have no memory budget because the retained heap of deep-update with patches, 1.8 KiB per output, measured 13–36 KiB in 23 of 120 processes of both builds, and a retained-heap budget failed one of the 30 runs on it at 12.8x.

Findings outside this PR

  • class-wide-update without freezing (a class instance with 1,000 fields) ran 2.4–3.9 times slower in all 40 processes that ran its freeze-on cells first, and at most 1.08 times slower in the 60 others. Both processes of a pair run in the same order, so the budget compares like with like, but the effect itself may deserve a look.
  • The slow state of the array moves affects about one process in six of either build; its cause in V8 is not identified here.

Verification

  • pnpm test:benchmarks: 35 tests pass; every commit passes the budget tests, the perf-testing format check, and lint.
  • Local node perf-testing/ci.mjs --self-control --group core: all 48 decisions pass.
  • The 112 earlier CI reports re-evaluated with the fastest-process comparison: all pass.
  • This PR's own budget run, whose library is unchanged from main: the four jobs took 5.9–7.4 minutes, and all 206 decisions passed, at most 1.053 for timing, 1.140 for allocation, and 1.007 for retained heap.

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

Coverage after merging perf/scenario-budgets into main will be

100.00%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src
   apply.ts100%100%100%100%
   array.ts100%100%100%100%
   constant.ts100%100%100%100%
   create.ts100%100%100%100%
   current.ts100%100%100%100%
   draft.ts100%100%100%100%
   draftify.ts100%100%100%100%
   error.ts100%100%100%100%
   index.ts100%100%100%100%
   interface.ts100%100%100%100%
   internal.ts100%100%100%100%
   makeCreator.ts100%100%100%100%
   map.ts100%100%100%100%
   original.ts100%100%100%100%
   patch.ts100%100%100%100%
   rawReturn.ts100%100%100%100%
   set.ts100%100%100%100%
   unsafe.ts100%100%100%100%
src/utils
   cast.ts100%100%100%100%
   copy.ts100%100%100%100%
   deepFreeze.ts100%100%100%100%
   draft.ts100%100%100%100%
   finalize.ts100%100%100%100%
   forEach.ts100%100%100%100%
   index.ts100%100%100%100%
   mark.ts100%100%100%100%
   marker.ts100%100%100%100%
   proto.ts100%100%100%100%

@unadlib
unadlib merged commit e6af4b0 into main Oct 8, 2026
8 checks passed
@unadlib
unadlib deleted the perf/scenario-budgets branch October 8, 2026 18:05
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