Skip to content

quest: delete adapter-waker-alloc, measured no-win - #7556

Merged
moq-bot[bot] merged 3 commits into
mainfrom
quest/m2/perf/adapter-waker-alloc
Nov 2, 2026
Merged

moq-bot[bot] merged 3 commits into
mainfrom
quest/m2/perf/adapter-waker-alloc

Conversation

@kixelated

@kixelated kixelated commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Deletes quest/m2/perf/adapter-waker-alloc.md and its link in the perf epic. The quest's Plan says a measured no-win deletes it, and the measurement is a no-win.

Verdict: a parking stream read in moq-tokio or moq-uring allocates no shared waker, before or after moq-dev/moq#7042.

Why: both adapters pass a read or write the task's own waker (waiter.context()) and never register the kio::Waiter on a kio list:

  • moq-uring: rs/moq-uring/src/transport/adapter.rs (poll_write, poll_write_buf, poll_read_chunk) and rs/moq-uring/src/transport.rs (poll_lease).
  • moq-tokio: rs/moq-tokio/src/transport/owned.rs (poll_write, poll_write_buf, poll_read_chunk, poll_lease).

Waiter::shared allocates its Arc<Waker> only on the first WaiterList::register, so a kio::wait future around such a read only constructs the waiter: a waker refcount bump, no heap. The uring QUIC driver underneath still keeps a per-stream kio::Park (rs/moq-uring/src/quic/driver/recv.rs, parked), and it is reused because the waker stays the same. The coding-layer async wrappers are not on the moq-lite data path either. Marking them #[deprecated] and running cargo check on moq-net, moq-tokio, moq-uring, and moq-relay finds production callers only in the handshakes and in IETF control, fetch/fill, and GOAWAY streams.

Measurements

The harness is a scratch moq-uring example that was not committed. It sets up a full session pair over loopback QUIC on moq_uring::Worker (like the session_lite bench) or on moq-tokio listen/connect on a current-thread runtime, both moq-lite. After a warmup, each iteration writes one group, then receives and reads it. The process is pinned to P-core 2 (Core Ultra 7 270K Plus) with taskset -c 2. perf stat -e cpu_core/instructions/u --control fifo counts only the measured loop, and a counting global allocator counts allocations across all threads. Each tree was exported with git archive and built --release (fat LTO): pre = 6eff65506e^ (before #7042), post = 6eff65506e (#7042), main = 0e9e828265. Three alternating rounds agreed to within 0.1% on the small groups and 1% on the 1 MiB groups. Shared-waker counts come from a separately instrumented build that counts Waiter::new and the Waiter::shared initializer.

Per group:

adapter group tree instructions allocations shared Arc<Waker>
uring 32 x 64 B pre 603.5k 438.1 45.8
uring 32 x 64 B post 611.4k 436.6 49.3
uring 32 x 64 B main 608.7k 347.6 55.8
uring 32 x 32 KiB pre 15.95M 621.3 77.8
uring 32 x 32 KiB post 15.97M 619.8 81.3
uring 32 x 32 KiB main 16.16M 530.8 87.8
tokio 32 x 64 B pre 715.0k 665.0 41.9
tokio 32 x 64 B post 722.5k 662.5 42.4
tokio 32 x 64 B main 741.2k 575.5 50.9
tokio 32 x 32 KiB pre 17.03M 781.3 57.9
tokio 32 x 32 KiB post 17.05M to 17.17M 778.3 58.4
tokio 32 x 32 KiB main 17.34M to 17.41M 691.5 66.9
  • refactor(net)!: transport polls take kio waiters through &self #7042 lowered allocations per group by 1.5 (uring) and 2.5 (tokio). It cost 1.0 to 1.3% more instructions on a group of small frames and about 0.1% on 1 MiB groups. That instruction cost does not come from allocation.
  • The shared wakers do not scale per read. Going from 1 to 32 frames adds 0.2 to 0.6 per frame on every tree, pre-refactor(net)!: transport polls take kio waiters through &self #7042 included, and none of them come from the adapters' read path.
  • Where main's 51 to 56 shared wakers per small group come from (backtraces captured at the Waiter::shared initializer):
    • 43 to 44: Waiter::noop() in resume::Reader::learn_end(None) (rs/moq-net/src/model/resume.rs), reached from poll_lowest, let_go, and poll_drop on every subscriber read. moq-dev/moq#7504 (draft) makes a no-op waiter register nothing, which removes these.
    • About 4: the session driver's long-lived waiter (one kio::wait per Handle::run / time::run) is rebuilt by Park::hold. The new waiter's first registration lands on the session-close Bridge (moq-uring adapter.rs poll_closed, moq-tokio owned.rs poll_closed). This matches a waiter that is not reusable(), because the driver parks on more than PARKED = 8 lists.
    • About 4 (uring only): each group stream's close watch, SendStream::poll_closed through its Bridge into the QUIC driver's park_finishing. That is per stream, not per read.
    • 0.12: the QUIC driver's park_readable, the only read-path registration.

Decisions needed

None. The Plan settles the outcome ("A measured no-win deletes this quest").

Choices made without asking

  • Measured with a scratch harness of my own rather than criterion's session_lite wall clock, so the counts are deterministic. moq-tokio has no session bench, and the relay's benches/session.rs runs on the in-memory transport, so it cannot reach the adapters. just bench BASE is wall-clock end to end, so I didn't use it.
  • Did not measure IETF fetch serving. By reading, one site does allocate per chunk on the adapters since refactor(net)!: transport polls take kio waiters through &self #7042: rs/moq-net/src/ietf/publisher.rs builds pin!(stream.closed()) inside the fetch chunk loop. Each chunk gets a fresh kio::wait whose waiter registers on the send stream's close Bridge. That is a cold path and a writer's close watch, not a parking read, so I left it as a follow-up below.

Follow-ups (for /quest-merge to decide)

  • Park::hold retires a waiter parked on more than 8 lists on every poll (about 4 Arc<Waker> per group on the session driver here). A relay session driver parks on many more lists, so it may churn harder there. This is worth measuring on a relay, and possibly raising PARKED or changing the retire rule.
  • IETF fetch chunk loop: keep one close watch per fetch stream instead of one per chunk (rs/moq-net/src/ietf/publisher.rs, the stream.closed() pinned inside the chunk loop).
  • refactor(net)!: transport polls take kio waiters through &self #7042's 1.0 to 1.3% instruction cost on small-frame groups, which is not allocation-related, is small. A frame-pointer profile of pre against post would show where it went, if it matters.
  • moq-dev/moq#7504 removes about 80% of the remaining per-group shared-waker allocations on the subscriber side (the learn_end no-op waiter).

Checks

quest check --base origin/main: 620 documents ok. No code changes.

Review

Round 1: quest review with Codex, gpt-6.1-sol, medium effort, on head fc8f35248ee4b953a1c088f32e9dfd721fbe38f3 against origin/main 0e9e828265. It reported no findings.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 2 commits November 1, 2026 04:42
moq-tokio and moq-uring hand a stream read or write the task's own waker
(`waiter.context()`) and never register the waiter on a kio list, so a
parking read allocates no shared waker. Pinned instruction and allocation
counts across #7042 show no per-read cost; the quest's Plan deletes it on a
measured no-win.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Adapter reads reuse their waker quest: delete adapter-waker-alloc, measured no-win Nov 1, 2026
@kixelated
kixelated marked this pull request as ready for review November 1, 2026 13:04
@coderabbitai

coderabbitai Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You'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 5 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 1e4b5b59-9781-4892-a423-82e83df42a86
Commits

Reviewing files that changed from the base of the PR and between 0e9e828 and fc8f352.

Files selected for processing: 2
  • quest/m2/perf/README.md
  • quest/m2/perf/adapter-waker-alloc.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

Share

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review by review (OpenAI)

Reviewed fc8f352.

No actionable bugs found. Deleting this quest and its index entry is a reasonable simplification: the inspected adapter reads forward the task's context without registering that waiter, and shared-waker allocation is lazy. There is no production API or wire change.

Verification limits: static inspection only. The scratch measurement harness and raw captures are not committed, so I did not independently reproduce the allocation/instruction results, run tests, or validate the reported checks. The separate session-driver and FETCH-close-watch costs remain distinct follow-up work.

(Written by OpenAI)

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Docs-only deletion of a completed quest; no behavioral surface.

  • Quest file removed — quest/m2/perf/adapter-waker-alloc.md (36 lines) deleted.
  • Epic link removed — the matching bullet dropped from quest/m2/perf/README.md.

The PR body's measurement supports the quest's own Plan ("a measured no-win deletes this quest"): moq-tokio and moq-uring both pass the task's own waker (waiter.context()) into a read or write and never register the kio::Waiter on a kio list, so a parking read never hits Waiter::shared's Arc<Waker> allocation. No dangling reference to adapter-waker-alloc (or the quest title) remains anywhere in the tree, and the README bullet list is still well-formed.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@moq-bot
moq-bot Bot merged commit 3e1137d into main Nov 2, 2026
2 checks passed
@moq-bot
moq-bot Bot deleted the quest/m2/perf/adapter-waker-alloc branch November 2, 2026 01:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant