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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,27 @@ Both ends share one current-thread runtime, so its CPU covers sending and
receiving alike. It is a local profiling tool rather than a nightly series: the
`moq-net` session bench owns qmux's series.

Measure qmux receive ownership at the relay client defaults (4 MiB connection,
1 MiB stream, 4 MiB send):

```bash
nix develop --command cargo bench --locked --manifest-path rs/qmux/Cargo.toml --bench receive
```

`qmux_receive` sweeps TCP and WebSocket framing, every qmux version, 1/8/64
sessions, and 1/8 streams per session. Both endpoints run on one thread over
bounded in-memory byte pipes; this isolates framing and receive ownership from
TLS, kernel sockets, and congestion. Each stream holds an unread 512 KiB lease,
so eight together fill the connection window. The untimed output reports live
requested heap bytes, including both endpoints, their tasks, the pipes, stream
tables and receive backings, per session. The shared source payload and runtime
are excluded. This measures retained allocations, not RSS; allocator counters
also run during timing. Every timed round reads and checks all payload bytes,
returns lease credit, and fills each stream again. Throughput counts delivered
payload once. Nightly's supplemental Rust benchmark leg records the series and
keeps the memory output with its raw results. The broader MoQ session transport
and version sweep remains in [its quest](/quest/m2/bench-transport-sweep.md).

Compare one multi-threaded Tokio runtime with the same number of independent
Tokio/epoll and io\_uring workers:

Expand Down
6 changes: 4 additions & 2 deletions doc/concept/moq-lite.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,10 @@ Under light congestion video drops the tail of a group; under heavy congestion
video stops and audio lags by at most 500 ms. No protocol change, just knobs.

A long group is paced by its fastest reader instead. Each hop receives at most
16 MB of a group past its fastest reader, then stops reading the group's stream,
so stream flow control pushes back hop by hop to the publisher, and a reader
its stream receive window of a group past its fastest reader, capped at 16 MB
(half the group's tail), then stops reading the group's stream, so the hop
holds about what its upstream peer can have in flight on that stream and
stream flow control pushes back hop by hop to the publisher. A reader
that keeps up loses nothing. A slower reader is never waited on: one that trails
the fastest by more than the group's 32 MB tail reaches a shed frame, and asks
for a new group on tracks that opt into [refresh](#refresh) or ends with
Expand Down
1 change: 1 addition & 0 deletions quest/m1/release-perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ subscribers.
## Required

- [Relay CPU per frame since #5926](/quest/m1/release-perf/relay-cpu-bisect.md) - the 18-56% rise in relay instructions per delivered frame is bisected, attributed, and its avoidable cost fixed
- [Pacing's per-frame report](/quest/m1/release-perf/pacing-report.md) - relay fan-out reads with pacing are within noise of the pre-pacing parent

## Related

Expand Down
22 changes: 22 additions & 0 deletions quest/m1/release-perf/pacing-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# [S] Pacing's per-frame report costs relay fan-out nothing measurable

## Goal

Relay fan-out reads (`group_relay_frames`) with fastest-reader pacing are
within noise of the pre-pacing parent for groups of 512 or more frames,
where today they are 4.6-8.1% slower, with pacing still bounding relay
memory exactly as before.

## Plan

Planned 2026-11-04 (maintainer): pacing's cost was accepted to finish
[fastest-reader pacing](https://github.com/moq-dev/moq/pull/7664), with this
trim as its follow-up. Measured in that PR, six rounds on a loaded host:
[moq-dev/moq#7388](https://github.com/moq-dev/moq/pull/7388), which added
pacing, against its parent `b51eb8c` is 4.6-8.1% slower on
`group_relay_frames` in all 12 cases with 512+ frames (64-byte payloads, the
worst case for a per-frame cost); 4-frame groups and `group_read_frames` are
unchanged. The likely cause is the per-read progress report #7388 added to
the batch read path; profile it first (`just bench-profile instructions`, as
[the relay CPU bisect](/quest/m1/release-perf/relay-cpu-bisect.md) does),
then report progress per batch or only when the pacing window can change.
2 changes: 2 additions & 0 deletions quest/m1/release-perf/relay-cpu-bisect.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Decided 2026-11-03 (maintainer, on [moq-dev/moq#7605](https://github.com/moq-dev

`Cursor::probe` is 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. On top of that, each time the cache aged out an old group, the eviction landed with every reader, all of which had already taken it, and forced each one to re-run the full check.

Added 2026-11-04 (maintainer) from [fastest-reader pacing](https://github.com/moq-dev/moq/pull/7664)'s comparison on a loaded host: `main` at `f37d964` reads `group_relay_frames` about 15% slower than `e7a5c10`. Pacing ([moq-dev/moq#7388](https://github.com/moq-dev/moq/pull/7388)) explains about 4% of that, and [its own quest](/quest/m1/release-perf/pacing-report.md) trims it; attribute the remaining ~10% here, on a quiet host.

Follow-up planning on 2026-11-03 (maintainer) added a load-independent mode to the driver: `just bench-fanout` counts the chat fan-out per delivered group on one pinned thread, through the model alone and through a relay over the in-memory transport, for any list of revisions. [The report](/.github/relay-cpu-bisect.md) attributes each step with it, and the fixes so far take `main`'s model path from 13,300 to 10,126 instructions per group (-23.9%) and the in-process session from 120,948 to 113,133 (-6.5%), at 512 subscribers.

Decided 2026-11-03 (maintainer, on [moq-dev/moq#7605](https://github.com/moq-dev/moq/pull/7605)), after the attribution:
Expand Down
2 changes: 1 addition & 1 deletion quest/m1/socket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pipe below; the second round above adds the acceptor to the session name:
- Long-lived `tcp` sessions rely on unbounded groups (since
[moq-dev/moq#7411](https://github.com/moq-dev/moq/pull/7411) a group has no
size cap, and each copy keeps a ~32 MiB tail). With one reader per copy,
[fastest-reader pacing](/quest/m1/unbounded-groups/fastest-reader.md) acts
[fastest-reader pacing](https://github.com/moq-dev/moq/pull/7664) acts
as TCP backpressure: a writer awaiting `group::Producer::ready` runs at most
16 MiB past its reader on each hop, and a reader that falls off the tail
fails with `Evicted` rather than skipping data. Where pacing doesn't reach
Expand Down
5 changes: 2 additions & 3 deletions quest/m1/unbounded-groups/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Decided 2026-10-28 (maintainer, after an audit of the plan and
shedding on pool debt alone let one live group grow without bound once the
group caps went. The 32 MB counts a group copy's live tail (its
`kio::log` run), and nothing is kept below it: no head, no refilled runs.
- With [fastest-reader pacing](/quest/m1/unbounded-groups/fastest-reader.md),
- With [fastest-reader pacing](https://github.com/moq-dev/moq/pull/7664),
the 32 MB is one budget split between "ahead" (bytes the fastest reader has
not read, bounded by pacing) and "behind" (bytes it has read, kept for
slower readers). Shedding never passes the fastest reader, and the ahead
Expand Down Expand Up @@ -76,7 +76,7 @@ superseded 2026-10-28 by flights.) When nothing serves a hole, the reader
gets `Evicted` and a joiner falls back to `Info::refresh`. The live
edge is paced by its fastest reader (decided 2026-10-25, superseding
2026-10-24's "never paced by readers"; see
[Fastest-reader pacing](/quest/m1/unbounded-groups/fastest-reader.md)), so a
[Fastest-reader pacing](https://github.com/moq-dev/moq/pull/7664)), so a
byte stream over MoQ loses nothing to a reader that keeps up. Groups are bounded by bytes, with each
frame's slot charged to the pool and the 32 MB tail. Front eviction landed
in [moq-dev/moq#6993](https://github.com/moq-dev/moq/pull/6993)
Expand Down Expand Up @@ -155,7 +155,6 @@ covered only at the model layer (maintainer, 2026-10-30).

## Required

- [Fastest-reader pacing](/quest/m1/unbounded-groups/fastest-reader.md) - every hop buffers at most a window past its fastest reader and pushes back through QUIC flow control
- [Flights](/quest/m1/unbounded-groups/flights.md) - a reader at a shed frame replays through a separate paced copy that hands over to the copy ahead
- [Byte holes](/quest/m1/unbounded-groups/byte-holes.md) - segments inside a frame shed like front frames and flights start at `(frame, byte)` on the current lite wip; the frame cap goes
- [JS byte holes](/quest/m1/unbounded-groups/js-byte-holes.md) - `@moq/net` mirrors the byte-offset wire and continues a frame cut off mid-way
Expand Down
Loading
Loading