Skip to content

feat(spmc): add competing queues - #305

Open
mxsm wants to merge 1 commit into
apache:mainfrom
mxsm:mxsm/212-spmc
Open

feat(spmc): add competing queues#305
mxsm wants to merge 1 commit into
apache:mainfrom
mxsm:mxsm/212-spmc

Conversation

@mxsm

@mxsm mxsm commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

Add opt-in bounded and unbounded asyncband::spmc queues for distributing work from one producer to competing consumers. Senders are non-cloneable and require &mut self; receivers are cloneable, with each consumed value delivered to exactly one receiver.

  • Reuse the private competing-consumer core shared with MPMC, preserving strict bounded capacity, synchronous unbounded sends, targeted notifications, cancellation handoff, and draining after sender disconnection.

  • Cover the public capability constraints with compile-fail tests and exercise delivery, cancellation, disconnection, backpressure, and concurrent 1P/8C consumption.

  • Add reproducible 1P/1C–1P/8C benchmarks against Asyncband MPMC, async-channel, and flume, with methodology and measurements in benchmarks/ecosystem/spmc/README.md.

  • Fixes feat(spmc): add a competing queue #212.

Validation

  • cargo x test: 562 tests and doctests passed, including 18 SPMC tests and five compile-fail checks.
  • cargo x check: feature matrix passed, including standalone spmc.
  • cargo x miri: 140 tests passed, including 17 SPMC tests.
  • cargo x bench: full suite passed; three additional serial SPMC runs used 100 samples per case.
  • cargo x lint: Clippy, rustfmt, Taplo, and typos passed; Hawkeye is blocked by the Windows checkout of the existing asyncband/MIGRATE.md symlink as a regular file. Targeted header checks for the added and moved source files passed.

Performance report

Measured on 2026-09-11 with an Intel Core i7-11700K (8 cores / 16 logical processors), 64-bit Windows 11 Pro 10.0.26200, Rust 1.96.1 (31fca3adb, x86_64-pc-windows-msvc), and the default optimized bench profile. The implementation is based on main at 8204e14. Peer versions: async-channel 2.5.0 and flume 0.12.0; runtime: Tokio 1.53.1; harness: Divan 0.1.21.

Each sample transfers 16,384 usize values from one producer to 1, 2, 4, or 8 competing consumers; bounded capacity is 64. The sender moves into one task without cloning or an added synchronization wrapper. Consumers drain until disconnection without fixed quotas, and each sample checks the total count and checksum. Queue and runtime construction are outside the measured region; data operations, disconnection, and task completion are inside.

The table reports the median of three run medians, in milliseconds per batch, from three serial runs of 100 samples per case with one batch per sample. No builds or tests ran alongside the measurements. Lower elapsed time is better. current denotes Tokio current-thread; 4 denotes four worker threads. MPMC uses Asyncband's shared core with one producer. The peer ratio is SPMC time divided by the faster of async-channel and flume, calculated before rounding.

Queue Runtime Consumers SPMC MPMC async-channel flume Peer ratio
bounded current 1 0.819 0.875 1.207 0.652 1.25x
bounded current 2 0.811 0.866 1.200 0.645 1.26x
bounded current 4 0.873 0.931 1.469 0.689 1.27x
bounded current 8 0.999 1.041 1.492 0.770 1.30x
bounded 4 1 1.054 1.123 1.480 0.898 1.17x
bounded 4 2 3.830 4.002 2.021 3.193 1.90x
bounded 4 4 8.008 7.979 6.074 4.889 1.64x
bounded 4 8 11.570 11.400 10.610 6.926 1.67x
unbounded current 1 0.565 0.613 1.314 0.521 1.08x
unbounded current 2 0.543 0.588 1.262 0.495 1.10x
unbounded current 4 0.542 0.588 1.254 0.498 1.09x
unbounded current 8 0.546 0.596 1.263 0.506 1.08x
unbounded 4 1 0.567 0.597 1.298 0.524 1.08x
unbounded 4 2 3.003 3.250 1.529 2.542 1.96x
unbounded 4 4 6.751 6.537 2.543 3.418 2.65x
unbounded 4 8 10.360 9.824 2.667 3.816 3.88x

The largest sustained gap is unbounded 1P/8C with four workers: 10.360 ms versus async-channel at 2.667 ms (3.88x). The same-core MPMC baseline takes 9.824 ms, about 5% less time than SPMC. Both serialize storage and endpoint state under a mutex and use semaphore-backed notifications; SPMC adds no extra queue lock or per-message allocation over MPMC. Together with the deterministic eight-waiter test verifying one ordinary notification and cancellation handoff, this suggests shared storage/waiter contention. This is an inference from code and measurements, not a lock profile. The gap above 3x remains a performance limitation; no measured topology reaches the 10x rejection threshold from #208 on this host.

Unbounded sends are synchronous for all implementations, so the producer can fill the queue before consumers run on a current-thread executor. Those rows measure draining rather than parallel consumer contention; use the four-worker results and targeted-wakeup tests to assess 1P/8C behavior. These measurements support core reuse on this host and do not establish performance across platforms or workloads.

Reproduce the repository benchmark suite with:

cargo x --help
cargo x bench --help
cargo x bench

For the focused measurements, run cargo x bench --no-run, then execute the ecosystem benchmark binary printed by Cargo with --bench --color never --sample-count 100 'spmc::' three times serially. The full methodology and recorded results are also in benchmarks/ecosystem/spmc/README.md.

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.

feat(spmc): add a competing queue

1 participant