Skip to content

Take the least of the rounds in the token allocation helper, not the median - #139

Merged
electricessence merged 1 commit into
masterfrom
test/token-allocation-measurement
Nov 15, 2026
Merged

electricessence merged 1 commit into
masterfrom
test/token-allocation-measurement

Conversation

@electricessence

Copy link
Copy Markdown
Collaborator

The token allocation test now takes the least of its 7 rounds per call variant instead of the median, because a round's average on the measuring thread is shifted by transient extra allocation that the median follows.

The failure was seen once, in the first CI run of the new test workflow (net11.0 Debug, Allocation_Channel, shapes Transform and AsyncPool: 655 bytes per call for the same token twice against 638 for one command token, slack 16). It was NOT reproduced locally in 65 runs of all six shapes (see Verification), so the cause is only partly explained, and this change is a hardening of the measurement, not a proven cure for that one failure.

What changed

Test only (TokenTerminalTests.BytesPerCall): the helper returns the least of the 7 per-round bytes-per-call values for each variant, not their median. The slack (16), the assertions and the library are unchanged. Allocation_Terminals, Allocation_Iterate and Allocation_Channel all use the helper and all get the change.

What the measurement showed:

  • The helper reads the per-thread allocated-bytes counter, so other threads' allocations (channel readers, the thread pool, the tiered JIT thread) cannot land in a window. That suspicion is ruled out.
  • The steady cost per call is exact and constant (384 or 392 bytes depending on the shape). In a share of the rounds the measuring thread itself sees transient excess: blocks of about 140 calls at +128 bytes, and single calls at about +920 bytes. About 24% of the rounds (202 of 840, Debug, 4 processors) were 1.5 KB or more above the shape minimum; it is much rarer in Release (2 of 420 rounds with 4 processors, 20 of 420 without the setting).
  • It is not tied to garbage collections (the same share of rounds with and without a gen0 collection), nor to the round index, and it persists with tiered compilation off (24 of 210 rounds), so tiered JIT is not the whole cause. The cause is therefore only partly explained.
  • The median of the rounds follows such shifts; the least does not. In an offline simulation over all runs and shapes, the least of the rounds was never worse than the median (worst excess over the command-token-only variant: 0 bytes with the least at 4 processors against 2 with the median; at 1 processor 3 against 3).

Verification

Before the change (diagnostic copy of the helper, since reverted; Allocation_Channel, all six shapes per run, 65 runs, none failed):

Setting Runs Result
net11.0 Debug, 4 processors 20 all pass
net11.0 Debug, no processor setting 10 all pass
net11.0 Debug, 1 processor 10 all pass
net11.0 Debug, tiered compilation off, 4 processors 5 all pass
net11.0 Release, 4 processors 10 all pass
net11.0 Release, no processor setting 10 all pass

After the change:

  • Allocation_Channel, net11.0 Debug, 4 processors: 20 of 20 pass; net11.0 Release, 4 processors: 10 of 10 pass.
  • Full Core test project: 10200 of 10200 pass on net11.0 Debug (4 processors), net11.0 Release, net10.0 Debug and net10.0 Release.
  • The guard still guards: with a temporary allocation of 64 bytes per call planted in the library when only the call token is live (reverted), all six Allocation_Channel shapes fail, for example "Transform: one call token allocates 480, one command token 392 ... slack 16".
  • After those runs only a comment in the helper was reworded before the commit.

Not done

  • The CI failure was not reproduced locally, and the cause of the transient excess is not isolated; no claim is made that this removes that failure on the CI runner.
  • A first planted allocation in the no-token branch of the channel path did NOT make the test fail (a throw at the same place did, so the line is reached; why the bytes did not show was not analyzed). The guard for that branch is therefore not proven by this change.
  • Slack, library and other tests are untouched.

🤖 Generated with Claude Code

…median

The first CI run of the new test workflow failed Allocation_Channel
(Transform and AsyncPool, net11.0 Debug): 655 bytes for the same token
twice against 638 for one command token, slack 16. The helper reads the
per-thread allocated-bytes counter, so another thread cannot add to a
window; the measuring thread itself sees transient extra allocation in a
share of the rounds (blocks of about 140 calls at +128 bytes, single calls
at about +920 bytes), and the median of the rounds follows it. The failure
was not reproduced locally in 115 runs; the cause is only partly explained.

BytesPerCall now returns the least of the 7 rounds per call variant.
The slack and the assertions are unchanged. Allocation_Terminals,
Allocation_Iterate and Allocation_Channel use the helper and benefit.
A planted per-call allocation still fails all six Channel shapes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@electricessence
electricessence merged commit a4f39de into master Nov 15, 2026
2 checks passed
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