Skip to content

perf(mpsc): use a segmented unbounded queue - #232

Closed
tisonkun wants to merge 3 commits into
mainfrom
codex/rewrite-unbounded-mpsc
Closed

perf(mpsc): use a segmented unbounded queue#232
tisonkun wants to merge 3 commits into
mainfrom
codex/rewrite-unbounded-mpsc

Conversation

@tisonkun

@tisonkun tisonkun commented Aug 28, 2026

Copy link
Copy Markdown
Member

Summary

  • Replace the mutex-protected unbounded buffer with a segmented queue whose producers reserve ordered slots atomically and whose single consumer reclaims completed blocks.
  • Preserve the current public API, FIFO ordering, error and cancellation semantics, and endpoint auto traits, including Unpin for pinned payloads.
  • Allocate message storage lazily and reduce slots per block for large payloads; release receiver-owned wakers and complete buffered cleanup after one payload destructor panics.
  • Reuse the existing benchmark workloads and add only channel construction, one-shot use, and a benchmark-only Crossbeam comparison for synchronous workloads. Strengthen existing concurrency coverage and add focused storage and cleanup regressions.

Part of #209.

Design Notes

The producer reserving a block's final slot installs its successor before publishing that slot. FIFO consumption then establishes that every producer using the old block has finished, allowing the unique consumer to reclaim it without a shared head CAS. Each block has up to 31 slots, reduced in power-of-two laps to keep ordinary slot storage within 32 KiB; oversized payloads use one slot per block. Empty channels allocate no message blocks, and drained channels retain at most one block. Block metadata and slots currently use separate allocations.

Publication, receiver registration, and final-sender disconnection participate in one sequentially consistent order to prevent lost notifications. Waker clone, drop, and wake callbacks run outside locks; receiver teardown removes the registered waker before draining messages. Sending and receiving may wait for an in-progress producer, and initialization and notification use internal mutexes. Exact Crossbeam provenance and the Apache-2.0 license option are recorded in the source and LICENSE.

The following comparison uses main 7092326c93b9d2cb91d7b80d1b8100ae1325ed57 and PR 6d827e4, compiled into the same benchmark executable on an Apple M4 Max, macOS 26.6.2, rustc 1.99.0-nightly (3d6c19bb9). Numbers are the median of five round medians with randomized implementation order, 100 samples per case, 1,000 iterations per microbenchmark sample, and one batch per throughput sample. The main library source is unchanged; a temporary path dependency supplies the baseline adapter. Peers are Tokio 1.53.1, async-channel 2.5.0, Flume 0.12.0, and Crossbeam 0.5.17. Crossbeam participates only in synchronous cases. Lower elapsed time is better; “best peer” means the fastest of these measured peers for that workload.

Workload Main This PR Best peer
Ready round trip 11.94 ns 6.90 ns Tokio 8.36 ns
Try round trip 11.03 ns 5.82 ns Tokio 7.66 ns
Create (destruction excluded) 9.74 ns 26.79 ns Flume 10.19 ns
One-shot channel lifecycle 98.86 ns 193.80 ns Crossbeam 61.44 ns
1 KiB inline burst, 1,024 messages 66.24 µs 80.37 µs Flume 67.83 µs
Native threads, 8 producers, 16,384 messages 356.90 µs 481.50 µs Crossbeam 437.90 µs
Async, 8 producers / 4 workers, 16,384 messages 451.50 µs 169.20 µs Flume 640.50 µs
Async 1 KiB payloads, 8 producers / 1,024-message bursts, 16,384 messages 3,455 µs 1,724 µs Tokio 1,969 µs

The results favor ready-message and contended asynchronous workloads but show material construction, one-shot, inline-payload, and native-thread regressions. They do not establish a uniform improvement over main. The full comparison covers all 21 existing unbounded workloads plus the two lifecycle cases.

Validated with cargo x test on stable and Rust 1.86.0, cargo x check, cargo x lint, cargo x bench --no-run, benchmark smoke execution, and the full cargo x miri workflow. MPSC concurrency tests additionally pass Miri seeds 1 through 3 with strict provenance and symbolic alignment checks. cargo semver-checks against current main passes all 223 applicable checks.

GitHub CI also passes the stable/MSRV test matrix on Linux, macOS, and Windows, the quality/feature/benchmark checks, and the Miri workflow with seeds 0 through 3.

@tisonkun tisonkun changed the title perf(mpsc): replace the unbounded queue backend perf(mpsc): use a segmented unbounded queue Sep 10, 2026
@tisonkun tisonkun closed this Sep 10, 2026
@tisonkun

Copy link
Copy Markdown
Member Author

Shelved in favor of main's simpler batched queue. The measured workload-dependent gains do not justify adopting the segmented queue's additional complexity at this time. The branch and PR history are retained for reference.

Follow-up #295 starts from current main and moves retired batch and segment-directory deallocation outside the shared state lock, with a separate main-versus-change benchmark comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant