Skip to content

Benchmark shards run ~2x slower when PRs overlap, hitting the 120-min timeout on doc-only changes #335

Description

@marius-bughiu

Observation

Benchmark shards run roughly 2x slower when more than one PR's Benchmarks workflow is in flight, which pushes the heaviest shards into the timeout-minutes: 120 cap and makes a fully-green PR unreachable.

Baseline — the last green run on main (30429534576, 2026-07-29):

shard 0 1 2 3 4 5 6 7
min 53 53 44 43 40 38 45 64

Same suite on #330 (30502468719), a documentation-only diff with zero IL change:

shard 0 1 2 3 4 5 6 7
min 103 106 85 87 78 71 83 >120 (still running)

Every shard is 1.75–2.0x its baseline. Shard 1 landed at 106 min against a 120 min cap; shard 7 has been running over two hours and will be killed.

On #331 (30502250767) — also documentation-only — benchmark (shard 1) ran from 00:18:08Z to 02:18:09Z, exactly 120 minutes, and was cancelled by the timeout. That single shard is the only non-success among that PR's 18 checks.

What was in flight during the window: three branches' Benchmarks runs overlapping — chore/code-review/lru-mru-hit-enumerator-docs, chore/code-review/intern-table-never-allocates-doc, and fix/issue-333-deque-clear-noop-version (which pushed five times in six minutes). At 8 shards each, that is up to 24 concurrent runner jobs.

Why this matters

  1. It is not the PR's fault and cannot be fixed by the PR author. Both affected PRs change only XML doc comments and Markdown. A contributor sees a red or perpetually-pending check caused entirely by what else happened to be running.
  2. It defeats the sharding design. benchmarks.yml explains that the matrix was widened 6 → 8 because "the heaviest shard measured ~65 min per slice … and the heaviest shard overran this timeout", and the comment ends: "Re-measure and widen the matrix again if shard durations creep back toward it." That advice assumes shard duration is a property of the suite. It is not — it is a property of the suite times how many other runs are competing, so widening the matrix chases a moving target.
  3. It may quietly corrupt the measurements, which is the worse failure. The same-runner A/B invariant is designed so hardware cancels out of the head-vs-base delta. That holds only if head and base see the same contention. With runs starting and finishing at staggered times, a shard's head slice and base slice can experience very different neighbour load, so the delta no longer cancels cleanly. A shard that times out is loud; one that completes under uneven contention silently publishes a skewed number to the dashboard.

Approaches to consider

  1. Serialize the workflow. A concurrency: { group: benchmarks, cancel-in-progress: false } at workflow level would queue runs instead of overlapping them. Slower to feed back, but it restores the one-run-at-a-time assumption the timeout and the A/B invariant were both calibrated under, and it makes durations predictable again.
  2. Don't run Benchmarks at all on diffs that cannot move a number. A paths-ignore for **/*.md, docs/**, and doc-comment-only changes would have skipped it entirely for both PRs here. This is the cheapest win and independently worthwhile — a 45-minute-per-shard suite on a Markdown edit is pure waste.
  3. Raise timeout-minutes — treats the symptom, and if contention is the cause the ceiling just moves.
  4. Worth confirming the diagnosis first: re-run the docs(StringInternTable): qualify the "never allocates" claim on GetOrAdd(string) #331 suite when nothing else is queued and check whether shard 1 returns to ~53 min. If it does, contention is confirmed and option 1 or 2 is the fix rather than a wider matrix.

I would do 2 first (cheap, obviously correct) and then 1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    automated:code-reviewPR or issue opened by celerity---code-review sweepqualityCode quality improvements

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions