Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Benchmark regressions in CI](/quest/m1/bench-ci.md) - PRs get a non-blocking comparison of the Criterion benches they affect, and a nightly trend on main alerts on regressions
- [Benchmark comparisons](/quest/m1/performance-comparisons.md) - retained evidence, repeated paired runs, and uncertainty for performance claims
- [#3126](/quest/m1/3126-moq-bench-every-readme-example-fails-to-parse-and.md) - moq-bench reports per-interval latency percentiles so the ramp leaves the steady state
- [Sans-IO session bench](/quest/m1/bench-session.md) - a moq-net bench drives publisher, relay, and subscribers over the in-memory transport, swept over publishers, subscribers, and frame size
- [Relay session bench](/quest/m1/bench-relay.md) - the same scenario through moq-relay's own connection handling
- [Bench coverage](/quest/m1/bench-coverage.md) - Criterion targets for moq-mux containers, the hang catalog, moq-auth verification, and moq-pattern matching
- [Relay profiling](/quest/m1/performance-profiles.md) - reproducible CPU and allocation captures under the existing workloads
Expand All @@ -88,6 +87,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics.
- [Watch worker](/quest/m1/watch-worker.md) - watch playback runs in a worker onto an OffscreenCanvas, so main-thread jank never stalls video or audio
- [Closure counters](/quest/m1/closure-counters.md) - a departed node's return never regresses the closure counters a consumer already saw
- [RTMP interleaving](/quest/m1/rtmp-interleaving.md) - isolate partial messages before optimizing assembly copies
- [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - with the default pool, relay memory plateaus at the expiry window on moq-transport as on moq-lite
- [Relay memory](/quest/m1/relay-memory.md) - remeasure what an announcement costs after prefix routes
- [PoP skipping](/quest/m1/pop-skipping/README.md) - short cold paths for unpopular broadcasts without losing warm backhaul dedup
- [Route cost in the JS origin](/quest/m1/route-cost.md) - the browser origin ranks routes by cost and hops like Rust instead of newest-first
Expand Down
1 change: 0 additions & 1 deletion quest/m1/bench-ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,4 @@ a GitHub App:
## Related

- [Benchmark comparisons](/quest/m1/performance-comparisons.md) - extends the same `bench/run.sh` with repeated paired rounds
- [Sans-IO session bench](/quest/m1/bench-session.md) - a low-noise end-to-end bench this job picks up
- [Bench coverage](/quest/m1/bench-coverage.md) - more targets for this job to track
8 changes: 2 additions & 6 deletions quest/m1/bench-relay.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,5 @@ transport session without widening the public API. If the seam costs more
than the bench is worth, say so and stop.

Reuse the scenario, the publisher and subscriber sweeps, and the delivery
accounting from the `moq-net` bench so the two results line up, and the
difference is the relay layer.

## Required

- [Sans-IO session bench](/quest/m1/bench-session.md) - supplies the scenario and harness this bench reuses
accounting from `rs/moq-net/benches/session.rs` so the two results line up, and
the difference is the relay layer.
31 changes: 0 additions & 31 deletions quest/m1/bench-session.md

This file was deleted.

24 changes: 24 additions & 0 deletions quest/m1/cache-expiry-growth.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# [S] Cached groups expire on the IETF path

## Goal

With the default cache pool, a relay's memory plateaus once groups start
reaching the expiry window, on moq-transport as on moq-lite, or the cause of
continued growth is found and fixed.

## Plan

In `session_delivery_broadcasts` with 4096 announced broadcasts and the
default unbounded pool (30 s expiry), IETF RSS reached 1.5 GB at 10 s, 3.5 GB at
30 s, and 4.9 GB at 50 s, still climbing past the expiry window. Lite
reached about 0.36 GB by 50 s. With a 16 MiB bounded pool both stay flat, so
the retained bytes are cached groups, not a leak elsewhere.

Reproduce with a focused test first. Unexpired groups at higher throughput,
expiry not running on a path IETF uses, or groups held outside the pool's
accounting would each explain it. Fix what is actually wrong. Consider
whether an unbounded default is the right default for an origin at all.

## Related

- [Relay memory](/quest/m1/relay-memory.md) - what an announcement costs in memory
3 changes: 3 additions & 0 deletions quest/m1/perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ row per io_uring worker.

- [Open contract](/quest/m1/perf/uring-open-contract.md) - plan concurrent WebTransport opening and cancellation

- [Lite route rescan](/quest/m1/perf/lite-route-rescan.md) - a moq-lite session's per-group cost stops growing with the routes its peer announced
- [Announce replay](/quest/m1/perf/announce-replay.md) - the initial announce set replays in linear time, so joins don't slow with the route count
- [Group cost](/quest/m1/perf/group-cost.md) - count and cut the allocations and time spent relaying one small group to one viewer
- [One enter per turn](/quest/m1/perf/uring-one-enter.md) - a parking turn pays one io_uring_enter, submits flush deferred completions, and SQEs per enter is a counter
- [Run to quiescence](/quest/m1/perf/uring-quiescence.md) - a received packet's reply is staged in the same turn, under a pass budget that keeps the fairness rule
- [Lock wait](/quest/m1/perf/lock-wait.md) - each worker reports time blocked on cross-worker locks, deciding whether the shared model needs work
Expand Down
22 changes: 22 additions & 0 deletions quest/m1/perf/announce-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [S] Linear announce replay on subscribe

## Goal

A session that subscribes to announcements receives the initial set in time
linear in the number of announced broadcasts, so a viewer joining a relay
with thousands of routes connects as fast per route as one joining a handful.

## Plan

The moq-lite publisher's initial replay (`AnnounceRun::init` in
`rs/moq-net/src/lite/publisher.rs`) de-duplicates by scanning the pending list
for every route it drains: `initial.retain` on Lite05+, `init.contains` on
the Lite01/02 init. That is quadratic in the replay size.

Measured with `session_join_broadcasts` (2026-09, one relay): lite join takes
199 µs with 1 announced broadcast, 517 µs with 64, and 8.9 ms with 1024,
about 8.7 µs per route at the top. IETF is 7.2 ms at 1024 with no quadratic
scan; its profile is SipHash hashing and `Path` comparison, so check whether
a faster hasher for path-keyed maps pays off on both.

Keep the replay's order and its last-update-wins semantics.
25 changes: 25 additions & 0 deletions quest/m1/perf/group-cost.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# [M] Measure and cut the fixed cost of relaying a group

## Goal

The fixed cost of relaying one small group to one viewer drops, measured as
allocations and time per viewer-group in the session benchmark, with no
change to what is delivered.

## Plan

`session_delivery_viewers` (2026-09) spends about 32 µs per viewer per
4-frame, 64-byte group over the in-memory transport, through a publisher
session, a relay origin, and a viewer session. No single function dominates.
The profile spreads it over `kio` waiter registration and parking,
`TrackState::evict_expired_scan` (4-5%), `Waiter`'s lazily allocated shared
waker (`Once::call`, about 3%, so waiters are created per poll), and
malloc/free (10-12%).

Count allocations per viewer-group first (a counting allocator in the bench,
as `moq-json`'s allocation bench does), then remove the largest sources. A
measured no-win abandons the quest, per this line's rules.

## Related

- [Owned decoding copies](/quest/m1/perf/coding-decode.md) - decode-side copies are part of the same per-group cost
29 changes: 29 additions & 0 deletions quest/m1/perf/lite-route-rescan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# [S] Lite subscriber polls only routes with a request

## Goal

A moq-lite session's per-group cost stops growing with the number of
broadcasts its peer has announced: delivery over a session holding thousands
of announced routes costs what it costs with a handful, as it already does
over moq-transport.

## Plan

`Announced::poll_serve` (`rs/moq-net/src/lite/subscriber.rs`) polls every
attached route's `Dynamic::poll_requested_broadcast` on every driver wake, so
each incoming group pays for every route, and each pending poll registers the
driver's waiter on every idle route's list. The IETF subscriber runs one task
per route and only wakes the one that got a request.

Measured with `cargo bench -p moq-net --bench session` (2026-09, one relay,
16 viewers each watching one broadcast):

- `session_delivery_broadcasts`: lite 445 µs at 16 announced, 15.2 ms at
4096; IETF flat around 458 µs. 57% of lite CPU is `WaiterList::register`
under `poll_requested_broadcast`.
- `session_delivery_scale` at 256 publishers x 256 viewers: lite 40 ms, IETF
16.5 ms, since every viewer session holds all 256 routes.

Wake only routes with a pending request. A task per route like IETF's or a
ready set both fit; pick by what keeps the driver's fairness rules. Land
with the before/after of those two groups.
4 changes: 4 additions & 0 deletions rs/moq-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,7 @@ harness = false
[[bench]]
name = "stats"
harness = false

[[bench]]
name = "session"
harness = false
Loading
Loading