Repository navigation
Time the benchmark's scaling rows on CPU time, and run its counts in parallel - #1352
Merged
Merged
Conversation
…parallel The clock-timed rows in tests/v2/test_benchmark.py read time.perf_counter(), which counts the time a loaded machine spends running other processes: under 24 busy loops on 12 cores 19-23 of the module's 62 tests failed in each of five runs, at up to 10.0x against a 6.0 bound. They now read the thread's CPU time, interleave the two inputs, and take more rounds only where the first ones read over the bound, so no threshold or baseline number moves; the thousand-names bound stays 5s, now of CPU time. The frame, call and shift counts lose the serial mark and run in the parallel suite, so a plain `uv run pytest` holds the band; only the four timed tests stay in the -n0 -m serial pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…claims #1352's review found two ways a loaded run could pass a real quadratic: more rounds were taken only at or over the bound, so a ratio that load DEFLATED (a planted 6.7x read 5.93x once at 24 busy loops) was never corrected, and the escalated loop stopped at its first reading under the bound. `_ratio` now takes all 35 rounds wherever its first 7 read within 20% of the bound or over it, and judges the last minimums; test_ratio_takes_more_rounds_only_near_the_bound holds that on a scripted clock and fails against both earlier loops. The weakest planted quadratic now fails every row in five loaded runs. Also corrects the review's wording findings: the counted guards include exact pins up to == 6, "read the same" was "passed", the extra rounds are not a retry, the 0.05x agreement was 0.35x, 21,093 was 21,065, and the wall-clock tables before 2026-11-11 are labelled as such. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… monotonically The fix commit's review: the 4.8 boundary row passed only because the fake clock's float sums landed a ulp over 6.0 * 0.8; 4.7 and 4.9 sit clear of it. And a ratio of two falling minimums is not monotone, which is why _ratio judges it once at the end -- the prose said otherwise. AGENTS.md's 21,093 becomes the population the decisions entry names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The row arrived from master written against the wall-clock helpers; as a _PREFIXED_SHAPES entry it already measures through _ratio under @serial. Its one-scan plant fails it on CPU time, quiet and beside 24 busy loops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
tests/v2/test_benchmark.py's clock-timed tests failed at random on a laptop running several agent sessions at once. Tests and docs only; nothing undernameparser/changes.What changed
time.thread_time,_CLOCK) instead oftime.perf_counter(). These are 23_SHAPESrows, 8_PREFIXED_SHAPESrows, 2_POLICY_SHAPESrows and the thousand-names backstop. The wall clock counts time the scheduler spends running other processes. CPU time doesn't, but it still counts everything the parse does on its own thread, C-level work and memory stalls included._bestbecame_ratio. It interleaves the small and large input (one parse of each per round), so a slow stretch is charged to both. It takes_ROUNDS= 7 rounds. Where those read within 20% of the bound or over it (_NEAR= 0.8), it takes all_MAX_ROUNDS= 35 and judges the last minimums.test_ratio_takes_more_rounds_only_near_the_boundholds this on a scripted clock._MAX_RATIO,_BASE,_PREFIXED_BASEand every frame baseline are unchanged. The one unit change is the thousand-names bound: still 5.0, now seconds of CPU time instead of wall time. CPU time can't exceed the wall time of a single-threaded parse, so the bound is no looser._ratiotest, run in the parallel suite now, so a plainuv run pytestholds the_CALL_BASELINEband, the link pin, the reread ratios and the shift guards. Only the four timed test functions keep theserialmark (-n0 -m serial, unchanged in CI)._CLOCK/_ratiocomments, the thousand-names docstring,tests/conftest.py, the marker description inpyproject.toml, the workflow comment, and the rootAGENTS.mdCommands block and the "Perf regressions" gotcha.CONTRIBUTING.md's command line didn't need to change.decisions/parse-costgets a dated entry and its Standing backstop line is amended.Before and after (py3.11, 12-core Apple silicon: 6 performance + 6 efficiency cores)
"Busy N" means N shell
while :; do :; doneloops started beside the run. The machine was never idle: other sessions held load at roughly 5–45 throughout, so "quiet" means no busy loops of mine.Five runs of
uv run pytest -n0 -m serial tests/v2/test_benchmark.pyper row:_ratio)_ratio)Busy 12 barely loaded the machine (macOS spreads 12 loops over 12 cores, leaving the test its own core). Busy 24 is the case that reproduces the reported failures.
Per-row ratios on the same samples, three runs at busy 24, load 18–75:
CPU time is still noisy under contention. A clean row (~4.0x) read up to 5.6x, about +40%. The likely cause is a thread moved onto an efficiency core or a cold cache, which is charged the slower run. Setting the thread's QoS to user-interactive didn't help, so it isn't in the PR.
The noise runs both ways. With extra rounds taken only at or over the bound (this PR's first push), a planted 6.7x quadratic (
given_part_titles) read 5.93x once at busy 24 and passed. The first push's loop could also stop on a dip under the bound. Review found both; the final_ratiofixes both:Every guard still fails on its regression
Each control below was run through the shipped estimator (
_ratio, or a verbatim copy for trees whosenameparserpredates this module's tables) and through the old_bestonperf_counter, quiet and at busy 24, three runs each. Historical controls ran on scratch worktrees of the named commit. Plants went into a scratch copy of master; nothing in this branch'snameparser/was touched._ratio, busy 24, two more runs)_ratio: ≥ 7.18 on every row, 5 runs_SHAPES,_PREFIXED_SHAPES,_POLICY_SHAPESrows-1 in listscanned len/D times), D=1 and D=2_ratio)Quiet, the two clocks fail the same rows under every plant, within 0.35x. Rows reading under 6.0 quiet with a given plant mean the plant is too weak for that row, and the wall clock reads them the same way.
Two notes on coverage:
_ShiftCountingListholds it now. CPU time does count memory stalls, so CI should see it as before.bracket_joined_membersandmaiden_clausehave no recorded control I could re-run. Neither the word-count plant nor the cut plant changes how their cost grows (each token is short), so they're covered only by the general result that the clocks agree when quiet.Frame tests in the parallel run
They pass:
-n0 path::test, all 28)test_cases.pyandtest_match.pyin one process (21,065 tests before them)--covon 3.11 (sys.settrace) and on 3.14 (sys.monitoring, CI's coverage interpreter), in parallel and-n0Decisions/parse-cost (#1170) records that an absolute count depends on what the process parsed before. The guards hold anyway because each one is one of:
only=/caller=,== 0up to== 6); none of those functions islru_cache'dTheir code is unchanged; only the mark moved. The
source-treeandinstalled-packageCI jobs, which run only the parallel pass, now run these 28 tests too.Reviews
Tests (pr-test-analyzer): no test dropped from either pass (62 → 34 serial + 28 untimed, disjoint); conftest deselection still behaves. Its optional-stopping finding is fixed (above). Two findings were addressed without changing code:
boundis passed separately from the assertion. Both read_MAX_RATIOand there is one caller, so this was left as is.Comments (comment-analyzer): wording findings fixed.
== 6.Design docs (design-docs-reviewer): the decisions entry was rewritten from the raw outputs: the 0.05x claim became 0.35x, 962301f's wall clock reads 4.07–4.50, the count after the 21,065 tests is corrected, and the deflation miss is recorded. That reviewer's deflation finding is what led to the
_NEARrule.Fix commit (code-reviewer): no case the first version refused now passes. It found three things, all fixed:
Checks
uv run pytest: 45,465 passed, 38 skipped, 4 xfailed (3.11)uv run pytest -n0 -m serial: 34 passed (3.11)uv run mypy,uv run ruff check: clean--cov: both passes green (the module re-run after the review fixes)UV_PROJECT_ENVIRONMENT: both passes green🤖 Generated with Claude Code