Repository navigation
perf(moq-net): a live reader's probe skips the fills, with exact per-commit attribution - #7605
Conversation
Quest-Claim: 8c4be0b7ab86d470
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
Co-Authored-By: GPT-6.1 Sol <noreply@openai.com>
# Conflicts: # quest/m1/release-perf/relay-cpu-bisect.md
Instructions mode now accepts space-separated BENCH_BASE revisions, so a bisect step measures its interior points side by side in each round. The paired revisions are the control, so instructions skip the unprofiled run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ide by side A base checked out at a fresh path rebuilt from scratch every run. Each base now keeps a worktree beside its target, and the revisions build in parallel on every allowed CPU, since fat LTO links on one thread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A newer client moved what historical relays spend per frame: with main's generator, 21d6da9's relay read 176k instructions per frame instead of the 156k it read under its own. BENCH_GENERATOR pins the client across a bisect's runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sampling interrupts run as the relay's kernel instructions, at a cost that follows its stack depth, so counting them biased deep-stacked revisions. Stacks now come from the five seconds after the counter window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A base checkout kept under target/ put a historical tree's tests where the repository's test filters find them. The compiler cache keys on remapped paths, so a fresh checkout still reuses each revision's target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Discover the allowed CPUs only on Linux, where sched_getaffinity exists and instructions mode runs; other hosts' bench modes no longer abort. - Summarize each worker count separately, so one configuration's regression cannot hide in another's range. - Run each revision build as its own process group, and stop the outstanding ones before cleanup removes the worktrees they read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nothing Every peek probes the reader's misses: it judges what arrived since the last probe for holes the live feed won't fill, then asks and resolves fills. A live reader that takes each group as it arrives never misses one, yet paid for all of it on every group. Now: - A probe with nothing to ask, no join, and no deferred gap skips the subscription read, the fills clone, and the ask/resolve loop (a priority change still reaches any joins the reader holds). - When only the feed's newest group moved, with nothing deferred or refused, a group the arrival walk serves is judged without the hole, backfill, and ledger passes, which would find nothing. - A reader's eviction inbox starts above what it handed out every group below: an evicted group it took is never a miss, but its landing counted as an eviction and forced a full judgement on every reader for every group the cache aged out. Instructions per group per subscriber in moq-net's track_fanout_group shape (one finished group, every subscriber takes it and parks), counted exactly: subscribers before after 512 13,669 10,780 (-21.1%) 64 13,888 10,981 (-20.9%) 1 27,287 24,500 (-10.2%) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oss two A window's runs are its fixed ranges and the layer its time ends walk on. Every read of the union grew each run across both sets, though most windows have no layer, and one set's runs are already merged and whole. A peek reads the window seven times per group, so a set alone now answers directly. Instructions per group per subscriber in moq-net's track_fanout_group shape, counted exactly, on top of the previous commit: subscribers before after 512 10,780 10,425 (-3.3%) 64 10,981 10,638 (-3.1%) 1 24,500 24,169 (-1.4%) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…growth Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relay instruction counts move 5-20% with host load, which hid the 2-4k steps the bisect was after. `just bench-fanout` builds two one-thread harnesses against each base and the current tree, and counts their user instructions with perf on one pinned CPU, less a run of zero cycles: - model: a finished group a cycle through the track model to every subscriber, building back to cdff3c8. - session: a publisher session into a relay origin and a viewer session out of it per subscriber, over the in-memory transport. Repeated counts agree within a few instructions per group. A revision whose session API predates the harness reports it unsupported, and callgrind outputs are kept on request for per-function attribution. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…outgrew" This reverts commit 1a5ceec.
This reverts commit 101ed5a and, before it, the enum boxing it needed. With 16 records, two moq-net integration tests fail (errored_end::an_errored_end_keeps_its_history_across_a_hop and quiet_catalog::a_quiet_catalog_reaches_an_unfloored_relay_reader_when_a_peer_resumes_past_it) and pass with 8. A read whose waiter parks on more lists than it records is retired and walks again; with more records, those reads stay parked, and something they depend on doesn't wake them. That needs its own investigation before the waiter can remember more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esumed floor #7708 judges from the reader's floor rather than a resumed copy's live floor, which can sit above history an unlimited reader still reads. The fast path for a live arrival still took the higher floor: it forgot those groups and left them out of the eviction inbox, so one evicted before the reader took it was never asked again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Design change: per the maintainer's 2026-11-03 decisions, this PR now changes production code as well as measuring. It adds a fast path to (Written by Claude Opus 5.5) |
…attributed costs Records the maintainer's 2026-11-03 decisions on #7605: the flat per-group feature costs are accepted in the release-perf epic with their counts, the waiter record limit becomes an m0 correctness quest, and shrinking group::Consumer joins the epic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 18 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head 249b368.
No actionable findings in this pass. The probe's fast path is guarded against changed budgets, windows, drops, evictions, deferred gaps and refusals; active fills still receive updated preferences and answers. The delivered-prefix eviction narrowing and one-layer window shortcut preserve the intended live-reader behavior, and the resumed-floor regression covers the earlier history concern. The bounded fast path is a reasonable direction for the measured cost.
Verification: GitHub-only static review of the model changes and tests. I did not run tests, benchmarks, or reproduce the reported measurements.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Live-reader probe fast path —
Cursor::probenow reads the subscription'saskinglazily, clones the track's fills only when something is asked, and skips theask/resolveloop while no fill is joined and no gap is deferred;Misses::judge_arrivalshandles the arrival-only transition, marking the new walked groups judged and trimming below the reader's floor (the resumed-live-floor fix from0e9b7ae6b). - Eviction inbox floor —
inbox_floorraises each reader's inbox span to its contiguous delivered floor, so evictions of groups it already took no longer land and force a full re-judgement; the full judge and the fast path share it. - Single-set window reads —
Layered::onlyletsfirst/lastread the one populated run set directly instead of growing across two. - Fan-out and instructions tooling —
just bench-fanoutwith thebench/fanout/{model,session}harnesses, theinstructionsprofile mode with rotated multi-revision relay runs, and the report/evidence under.github/. - Quest records — the maintainer's 2026-11-03 decisions, the accepted feature costs, and the waiter/group-consumer follow-up quests.
I traced the fast path against the full judge for arrivals, evictions, ledger changes, deferred/refused gaps, and the is_idle skip, and could not produce a divergent reader-visible outcome; the new regression test exercises the resumed-floor case round 1 found, and the remaining test change reflects the intended inbox semantics. No public API or wire changes.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
|
This run was cancelled before it finished. |

Problem
The relay's instructions per delivered frame grew after moq-dev/moq#5926. The quest asks for each rise to be found, attributed, and fixed when accidental. The first round bisected the relay counts: the two large accidental rises were already gone from
main, and three decisions were left for the maintainer.Decisions
Decided 2026-11-03 (maintainer):
Cursor::probeon every peek (moq-dev/moq#6171, about 8.6k instructions per frame):Decided 2026-11-03 (maintainer), after the attribution:
Costs measured and not cut:
The waiter's list records:
group::Consumermoves:just bench-fanout: a read moves a small group consumer.Land this PR now as a checkpoint, or keep it open until the quest's remaining measurements are done:
main, and re-pairingmainagainst the old endpoint.No existing quest overlapped either follow-up. They link the related task-capacity (a task slot holds a whole waiter) and relay-allocs (boxing must not add a per-group allocation) quests.
Approved in follow-up planning the same day: give the driver a load-independent mode for the chat fan-out and use it for the feature-cost investigation, with the quiet test hosts available for relay runs.
Cursor::probeis the relay's check for groups a reader has missed. Each time a subscriber asks for its next group, the probe looks at what arrived on the track since its last look and decides whether any group the reader still wants has left the cache without the live feed delivering it (evicted, skipped, or from before the feed started), and asks upstream to fill those. A live reader that takes each group as it arrives never has such a gap, yet it paid for the whole check on every group: locking the subscription and the track's shared state, cloning the track's fills, walking the arrival ledger, checking each new group for holes, and running the ask-and-answer loop. Each time the cache aged out an old group, the eviction also landed with every reader, all of which had already taken it, and forced each one to re-run the full check.What changed
A load-independent driver mode,
just bench-fanout, counts the chat fan-out per delivered group on one pinned thread, for any list of revisions:model: one finished group a cycle through the track model to every subscriber. It builds against every revision back to cdff3c8f95.session: a publisher session into a relay origin and a viewer session out of it per subscriber, over the in-memory transport. It builds back to 2026-11-01.Each count is user instructions from
perf statfor a run of many cycles less a run of zero, the median of three. Host load doesn't move it; each process's hash seeds move it by up to 0.5%. Callgrind attribution, and a comparison of two revisions by function, are kept on request.Fixes, each its own commit, counted together in one run at 512 subscribers per delivered group:
mainAfter merging
mainat 619df6e1b3, against it: model 13,368 to 10,119 (-24.3%), session 121,114 to 113,143 (-6.6%).errored_end::an_errored_end_keeps_its_history_across_a_hopandquiet_catalog::a_quiet_catalog_reaches_an_unfloored_relay_reader_when_a_peer_resumes_past_it) and pass with 8. A read whose waiter parks on more lists than it records is retired and walks again; with more records those reads stay parked, and something they depend on doesn't wake them. The revert and its enum boxing are separate commits; the change's own commit message overstated its model effect, measured with frame pointers forced.On the quiet test host
nixos2(one-minute load at most 4.7), chat over io_uring went from 154.5k [154.1-155.0] instructions per frame onmainto 148.9k [148.3-150.8] with the probe and window changes (-3.6%), four rounds each, every cell at 20,000 frames/s with no loss. QUIC, crypto, and syscalls are most of the relay's cost, so the model's -24% shows as a few percent there.quest gateisn't installed onnixos2, so its load and other agents were checked before each run.Attribution
The report has the full tables. The model path cost 4,285 instructions per group at the old endpoint and 13,300 on
main. The features asked about, each against its parent (model, 512 subscribers):In-tree qmux (moq-dev/moq#6231) and receive leases (moq-dev/moq#5878) are QUIC-level, which neither scenario reaches. A relay run pairing each with its parent on
nixos2got one round (qmux 116k to 122k, leases 130k to 132k per frame) before other agents started fuzzing and compiling there; it was stopped, and neither is attributed yet.Most of the model's growth came later, in a few steps found exactly: moq-dev/moq#6171 +4,264 and moq-dev/moq#7339 +1,154, which the probe fast path recovers most of; then moq-dev/moq#7542 +876 (session +3,054), moq-dev/moq#6995 +558 (session +3,290), and moq-dev/moq#7052 +365.
For decision 3, the session path's largest remaining self costs are moves of large values (about 9.4k per group:
group::Consumeris 280 bytes, moved by value throughpoll_peek_group,take, and the viewer's serve loop) and waiter registration (about 7.2k, with the record limit above). The rest of the lite publisher's serving is the per-group uni stream's open, write, and close, spread across small costs.Impact
just bench-fanout,bench/fanout/,bench/fanout.py; the instructions mode buildsBENCH_BUILD_JOBSat a time.Review
Reviewer: Codex (gpt-6.1-sol, medium effort) via
quest review --base origin/main, one session since the design change (--new).rs/moq-net/src/model/track/miss.rs:1000: the live-arrival fast path judged from a resumed copy's live floor, losing an unlimited reader's history below it once evicted. Fixed by judging from the reader's floor, as the full judgement does since moq-dev/moq#7708. The regression testhistory_below_a_resumed_live_floor_is_refilled_after_a_live_arrivalfails with the old floor ("asked []") and passes now.main, the two follow-up quests, the epic's accepted costs, and the recorded decisions): no findings. It was the design checkpoint's fourth round; the approach stands, since nothing was left to fix.Head 249b3687fc adds only a conflict-free merge of
mainsince round 4, which the guide exempts from a new round, and this PR body records the checkpoint decision; no new round ran.Checks
quest gate just check origin/mainon 249b3687fc, after mergingmain, passed every module but two.check carlaismain's own break since moq-dev/moq#7691 (demo/carla/sim/moq_carla_sim/publish.py:202misses the newtimestampargument), which this branch doesn't touch; open moq-dev/moq#7755 fixes it.check flakefailed on a temporary directory under.tmp/that another module created and removed during evaluation; run alone, it is skipped here because the local Nix builder can't create user namespaces. It passed in the full run on 77da888fe4.quest check --base origin/mainpasses (567 documents after the merge).main's merge, whichjust checkcovered.bench/fanout.test.pyand the rest of the bench tests, Ruff, and shellcheck pass. Both driver modes ran end to end.quest gate, at one-minute loads up to 40 on this host; the fan-out counts don't depend on load. Relay runs were onnixos2, which was idle when its first run started; a second run there was stopped when other agents started work.kernel.perf_event_max_sample_ratewas restored after each run on both hosts.Follow-ups
targetdirectories under disk pressure, which broke two long sweeps here;bench-fanoutgainedBENCH_FANOUT_TARGETto build outside it.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code