Skip to content

Throughput benchmarks

Ring throughput benchmarks

This page reports sustained throughput (M items / second) across every ring primitive in the substrate at the matrix of (locale, capacity, producer-count, consumer-count) cells the primitive is configured to accept. All numbers are direct measurements - no statistical model, no projection.

The full machine-generated table lives on the throughput results page (regenerated by re-running the sweep). This page summarises the headline numbers and links the rest.

Reproducing the numbers

The bench harness is a single binary (bench_throughput) that dispatches per primitive; the matrix on the results page is the cross product of the cells it lists, at n_items = 100000 with 3 runs per cell (median published).

cargo build --release --example bench_throughput -p subetha-cxc
# One cell per invocation; emits one line of key=value pairs.
./target/release/examples/bench_throughput <primitive> <locale> <capacity> <n_producers> <n_consumers> <n_items>

What’s benched

Primitive argSourceShapeLocales
spscSharedRingSpsc1P / 1C Lamport pairanon
mpscSharedRingMpscNP / 1C composed Lamport ringsanon
mpsc-fifoSharedRingMpscFifoNP / 1C single Vyukov ringanon
mpmcSharedRingMpmcNP / NC composed Lamport gridanon
vyukovSharedRingNP / NC Vyukov MPMC, global FIFOanon / file / shmfs
broadcastSharedBroadcastRing1P / NC fan-outanon / file / shmfs
pubsubPubSubRing1P / NC absolute-positionanon / file / shmfs
adaptive-{spsc,mpsc,mpmc,vyukov}AdaptiveRingshape morphs at runtime; UNPINNED dispatch (one Acquire + match + delegate per call)anon / file / shmfs
adaptive-pinned-{spsc,mpsc,mpmc,vyukov}AdaptiveRing via PinnedRingPINNED hot path: native primitive speed, validity-checked at meaningful intervalsanon / file / shmfs
locale-adaptiveLocaleAdaptiveRingany shape across any locale; unpinned dispatch (locale tag load + delegate)anon / file / shmfs
capacity-{spsc,mpsc,mpmc,vyukov}CapacityAdaptiveRingUNPINNED dispatch through wrapper (one ArcSwap + stale-walk + AdaptiveRing dispatch + delegate per call)anon / file / shmfs
capacity-pinned-{spsc,mpsc,mpmc,vyukov}CapacityAdaptiveRing via PinnedCapacity + PinnedRingPINNED hot path: skip wrapper AND skip AdaptiveRing dispatch; near-nativeanon / file / shmfs
capacity-broadcast / capacity-pinned-broadcastCapacityBroadcastRing (un/pinned variants)slot count morphs; pinned hot-loops on the inner SharedBroadcastRinganon / file / shmfs
capacity-pubsubCapacityPubSubRing (publish path holds chain lock by design; subscribe path is lock-free)slot count morphsanon / file / shmfs
crossbeamcrossbeam-channel::bounded (external baseline)NP / NCn/a
std-mpscstd::sync::mpsc::sync_channel (external baseline)NP / 1Cn/a

For each primitive the sweep runs every combination of:

  • Capacity: 256, 1024, 4096, 16384
  • Producers: 1, 4, 8
  • Consumers: 1, 4, 8 (and 2 / 4 / 8 for fan-out)
  • Locale: anon, file, shmfs (where the primitive supports it)
  • Runs: 3 per cell by default (-Runs raises it); min / median / mean / max reported, and median is the headline because run-to-run jitter at small workloads has long tails

Pinned vs unpinned: what the two numbers mean

The adaptive wrappers (AdaptiveRing, CapacityAdaptiveRing, LocaleAdaptiveRing) expose two API surfaces:

  1. Unpinned dispatch - ring.try_send(...) / ring.try_recv(...) calls go through the wrapper’s per-call dispatch (read the shape tag / load the active state / fall through to the inner primitive). This is what the adaptive-* and capacity-* table rows measure.
  2. Pinned hot path - ring.pin_current_shape() returns a PinnedRing (or PinnedCapacity -> pin.ring() -> further pin) that exposes the inner primitive directly. Hot loops pin.spsc_try_pop(...) etc. without per-call dispatch. The pin is invalidated on morph; the caller checks pin.is_still_valid() periodically and re-acquires. This is what *-pinned-* rows measure.

The pinned path approximates the underlying native primitive (spsc, mpsc, mpmc, vyukov) numbers because the morph-aware dispatch is bypassed for the duration of the pin. Production code that has stable shape / capacity / locale takes the pin; production code that’s actively morphing pays the dispatch cost on every call.

What the numbers mean

  • M items / s = total items pushed-and-popped divided by wall time.
  • Each item is a 52- or 56-byte payload (matches the per-slot payload size of the primitive).
  • Producers + consumers run on dedicated OS threads. The bench harness’s main thread participates only as the !Sync MPSC / MPSC-fifo consumer (those types’ Consumer is a per-thread token by design).
  • “anon” = in-process anonymous mmap. “file” = mmap-backed file in %TEMP% (cross-process via OS page cache). “shmfs” = named shared-memory region (cross-process, RAM-resident, never touches the page cache; on Linux this is /dev/shm, on Windows it’s named-section mapping).

What the numbers don’t mean

  • Per-op latency (mean / stddev / p99) is NOT in these tables - it’s measured by the criterion benches under crates/subetha-cxc/benches/ (look for capacity_adaptive_ring.rs, capacity_broadcast_ring.rs, capacity_pubsub_ring.rs, plus the pre-existing shared_ring.rs and shared_broadcast_ring.rs). Run cargo bench -p subetha-cxc --bench <name> to reproduce.
  • These numbers are SINGLE-machine. Cross-host throughput rides on the QUIC / TCP bridges (separately benched) and depends on network conditions.
  • The “best per primitive” table picks the best-of-matrix cell. Real workloads won’t always hit those cells; the per-cell deep-dive tables are the honest picture.

Bench audit notes

The harness reports the effective producer/consumer count, not the requested one. For example: SPSC always runs 1P/1C regardless of CLI request (the underlying primitive enforces this at compile time via the !Sync Producer/Consumer token); broadcast / pubsub ignore the producer arg (1 producer is the only mode). This is why effP and effC columns matter - they answer “what did the bench actually do” rather than “what did I ask for”.

When a cell can not run (e.g. SPSC at locale=shmfs has no constructor, or 1P/8C requested where max_producers must be >= max_consumers), the harness emits a SKIP line with the reason. The “Skipped cells” section at the bottom of throughput_results.md lists them.