Skip to content

perf(mpmc): coordinate queue values and waiters under one lock - #314

Open
orthur2 wants to merge 2 commits into
apache:mainfrom
orthur2:perf/mpmc-single-lock
Open

perf(mpmc): coordinate queue values and waiters under one lock#314
orthur2 wants to merge 2 commits into
apache:mainfrom
orthur2:perf/mpmc-single-lock

Conversation

@orthur2

@orthur2 orthur2 commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

A successful try_send currently locks the value queue and then the receiver semaphore. A bounded try_recv does the same with the sender semaphore. I moved values, endpoint counts, and both waiter queues under one mutex so each queue update and its waiter selection happen in the same critical section.

The tests cover cancellation, waker reentrancy, and disconnection notifications. The new microbenchmarks cover ready operations, pending receives, and waking blocked senders or receivers alongside the existing contention workloads.

Design Notes

I kept the existing rule that a notification makes an operation runnable without reserving a value or slot. Waiters are selected in queue order. An operation that loses the resource requeues at the back; cancellation forwards a notification only while its resource remains available.

A receiver registers only after observing an empty queue under the same mutex. Each subsequent push selects and unlinks the first receiver. While receivers remain queued, buffered values cannot outnumber notified receivers that have not yet polled or been dropped. Cancellation either transfers the notification for a remaining value or leaves the next push to notify a waiter. Bounded senders follow the same argument with free slots.

Waker callbacks, including clones, and message destructors run outside the lock. Selected waiters are woken before retired wakers or a cancelled sender's value are dropped. On disconnection, every wake is attempted before the first panic is propagated.

Benchmarks

Measurements compare this PR at e3ffa44 with upstream main at 8d6db15. Both revisions were built separately with the same benchmark sources, Cargo.lock, and optimized bench profile, then run sequentially in alternating order on an Apple M5 (10 CPU cores), macOS 26.4 (aarch64), and Rust 1.96.0.

The ecosystem suite uses 16,384-value batches and bounded capacity 64. Each run collects 20 samples per case. The table reports the median of five per-run medians for each Asyncband revision; async-channel 2.5.0 and flume 0.12.0 pool the ten runs from both binaries. Tokio 1.53.1 runs either on a current-thread runtime or with four workers. Lower is better.

With Tokio as the runtime, Asyncband’s median batch times were 12.7% to 42.2% lower than main and the lowest among the three libraries in all 16 cases on this host. Blocking-thread batch medians were up to 42.9% lower, with no change in bounded 8P/8C. The largest remaining gap is unbounded blocking 1P/8C, which also varied most across repeated runs: Asyncband took 11.59 ms, compared with 2.10 ms for async-channel and 4.49 ms for flume.

Ecosystem

Case main 8d6db15 This PR Elapsed change async-channel flume
bounded / blocking threads / 1P/1C 1.25 ms 1.17 ms -7.1% 1.43 ms 1.20 ms
bounded / blocking threads / 1P/8C 26.29 ms 23.21 ms -11.7% 30.45 ms 20.11 ms
bounded / blocking threads / 8P/1C 26.24 ms 22.77 ms -13.2% 23.63 ms 17.70 ms
bounded / blocking threads / 8P/8C 6.21 ms 6.21 ms -0.0% 8.12 ms 10.16 ms
bounded / Tokio current-thread / 1P/1C 331.2 µs 223.9 µs -32.4% 423.6 µs 314.9 µs
bounded / Tokio current-thread / 1P/8C 412.0 µs 283.0 µs -31.3% 529.0 µs 371.8 µs
bounded / Tokio current-thread / 8P/1C 369.8 µs 254.6 µs -31.2% 479.9 µs 386.0 µs
bounded / Tokio current-thread / 8P/8C 377.2 µs 257.5 µs -31.7% 483.9 µs 347.6 µs
bounded / Tokio 4 workers / 1P/1C 424.0 µs 336.3 µs -20.7% 727.4 µs 414.4 µs
bounded / Tokio 4 workers / 1P/8C 1.68 ms 994.8 µs -40.8% 2.63 ms 1.92 ms
bounded / Tokio 4 workers / 8P/1C 1.70 ms 980.6 µs -42.2% 2.58 ms 2.73 ms
bounded / Tokio 4 workers / 8P/8C 1.29 ms 777.0 µs -39.9% 1.51 ms 1.41 ms
unbounded / blocking threads / 1P/1C 596.3 µs 514.9 µs -13.7% 727.8 µs 536.0 µs
unbounded / blocking threads / 1P/8C 20.30 ms 11.59 ms -42.9% 2.10 ms 4.49 ms
unbounded / blocking threads / 8P/1C 839.5 µs 568.4 µs -32.3% 2.05 ms 584.3 µs
unbounded / blocking threads / 8P/8C 1.22 ms 874.9 µs -28.3% 1.81 ms 1.01 ms
unbounded / Tokio current-thread / 1P/1C 227.0 µs 193.1 µs -14.9% 414.9 µs 232.9 µs
unbounded / Tokio current-thread / 1P/8C 228.5 µs 197.9 µs -13.4% 413.6 µs 235.2 µs
unbounded / Tokio current-thread / 8P/1C 231.0 µs 197.2 µs -14.6% 415.7 µs 234.1 µs
unbounded / Tokio current-thread / 8P/8C 229.3 µs 198.8 µs -13.3% 414.7 µs 239.4 µs
unbounded / Tokio 4 workers / 1P/1C 247.1 µs 215.7 µs -12.7% 432.9 µs 255.1 µs
unbounded / Tokio 4 workers / 1P/8C 879.6 µs 563.2 µs -36.0% 900.6 µs 756.4 µs
unbounded / Tokio 4 workers / 8P/1C 624.7 µs 417.3 µs -33.2% 942.7 µs 479.9 µs
unbounded / Tokio 4 workers / 8P/8C 630.0 µs 491.6 µs -22.0% 1.14 ms 624.5 µs

The seven single-threaded microbenchmarks use eight alternating rounds, each with 100 samples of 256 iterations. Each cell is the median of the eight per-run medians. Microbenchmark medians were 11.8% to 40.5% lower.

Microbenchmarks

Benchmark main 8d6db15 This PR Elapsed change
mpmc::bounded::send_then_recv 18.99 ns 14.67 ns -22.7%
mpmc::bounded::try_send_then_try_recv 15.73 ns 10.04 ns -36.2%
mpmc::bounded::wake_blocked_sender 46.41 ns 27.61 ns -40.5%
mpmc::unbounded::repoll_pending_receiver 10.12 ns 6.95 ns -31.3%
mpmc::unbounded::send_then_recv 13.78 ns 12.15 ns -11.8%
mpmc::unbounded::send_then_try_recv 12.15 ns 10.04 ns -17.4%
mpmc::unbounded::wake_pending_receiver 43.08 ns 29.24 ns -32.1%

Validation

cargo x lint, cargo x check, cargo x test, cargo +1.86.0 x test, and cargo x miri passed on e3ffa44. Under Miri, the MPMC suite ran 19 tests and ignored its 2 Tokio runtime tests.

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