perf(mpmc): coordinate queue values and waiters under one lock - #314
Open
orthur2 wants to merge 2 commits into
Open
perf(mpmc): coordinate queue values and waiters under one lock#314orthur2 wants to merge 2 commits into
orthur2 wants to merge 2 commits into
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A successful
try_sendcurrently locks the value queue and then the receiver semaphore. A boundedtry_recvdoes 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
e3ffa44with upstreammainat8d6db15. 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
8d6db15The 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
8d6db15mpmc::bounded::send_then_recvmpmc::bounded::try_send_then_try_recvmpmc::bounded::wake_blocked_sendermpmc::unbounded::repoll_pending_receivermpmc::unbounded::send_then_recvmpmc::unbounded::send_then_try_recvmpmc::unbounded::wake_pending_receiverValidation
cargo x lint,cargo x check,cargo x test,cargo +1.86.0 x test, andcargo x miripassed one3ffa44. Under Miri, the MPMC suite ran 19 tests and ignored its 2 Tokio runtime tests.