Skip to content

Deque Dispatcher

DequeDispatcher

Rust Edition Layout Routing Cross-Process

DequeDispatcher owns one handle of each SharedDeque (Chase-Lev) / SharedDequeKhpd / SharedDequeLoh / SharedDequeUrd / SharedDequeKhl variant the host has configured, and routes per call based on a caller-supplied WorkloadShape. No single variant wins every shape; the dispatcher picks the right one per call.

The “per-shape routing” primitive. Sits one layer above the five MMF-deque variants. Callers express the workload’s shape (number of thieves, batch size, idle-wait hint) and the dispatcher picks the variant that minimizes per-item cost for that shape.

Routing rules (DequeDispatcher::pick, SubEtha-empirical):

WorkloadPickWhy
Multiple thieves (n_thieves >= 2)UrdPer-thief mailbox = zero CAS contention on the steal site.
Single thief AND wait_idle = trueUrdHardware-mediated wake via UMWAIT on WAITPKG-capable silicon.
Producer batches K >= 2 items per call, single thiefKhlThe three-lever hybrid: KHPD’s 3-items-per-Release-store, LOH’s per-batch counter amortization, AND Chase-Lev’s owner-private tail. Measured 1.55x KHPD at K=64.
Per-item dispatch, single thiefChaseLevLowest constant per push; no batch to amortize.

KHPD and LOH stay available as explicitly-configured backends (and as fallbacks), but the shape router sends every batched single-thief call through KHL. pick_by_signature derives the same answers from the variants’ K-axis signature sets, and pick_with_fallback walks primary -> KHL -> KHPD -> LOH -> Chase-Lev -> URD, returning the first variant configured on this dispatcher (None only when none are). is_configured(variant) and the per-variant getters (chase_lev() / khpd() / loh() / urd() / khl() returning Option<&Arc<..>>) expose the configured set.

Cross-process end-to-end

The dispatcher lives in the producer process. Each variant it owns is backed by its own MMF file path; consumer processes open those same paths via the variant’s open_as_thief / open constructors.

The dispatcher_demo example demonstrates the full parent / child pattern: the parent dispatches 30 per-item jobs (Chase-Lev route) plus a 60-item batch (KHPD route) through one dispatcher; the child opens both MMF deques as a thief and drains them in parallel threads, summing the ids it sees per backend and exiting 0 if the sums match.

Run:

cargo run --release --example dispatcher_demo -p subetha-cxc

Output (Zen+ R7 2700, Win11):

[parent] dispatched 30 request-reply jobs in 24.3us (0.80 us/job)
[parent] dispatched 60-item batch in 4us (66.67 ns/job)
[child] drained cl=30 (435) + khl=60 (3570) = 90 items, sum 4005 (expected 4005)
[parent] child drained 90 items bit-exact through dispatcher (Chase-Lev + KHL routes confirmed)

The batched route is 12x faster per-item than the request-reply route end-to-end in the captured run. The dispatcher’s per-call routing + delegation overhead (the cost of pick_with_fallback + the variant match) is observable in the per-job cost; in the captured run (Zen+ R7 2700) the delegation overhead is sub-50 ns.

API surface

use subetha_cxc::{DequeDispatcher, DequeVariant, LineItem, WorkloadShape};

// Build a dispatcher with Chase-Lev + KHL configured.
let dispatcher = DequeDispatcher::builder()
    .with_chase_lev("/tmp/cl.bin", 1024)?
    .with_khl("/tmp/khl.bin", 256)?
    .build();

// Dispatch one item under a request-reply shape.
let item = LineItem::new(&42u32.to_le_bytes())?;
let variant = dispatcher.dispatch_one(WorkloadShape::request_reply(), item)?;
assert_eq!(variant, DequeVariant::ChaseLev);

// Dispatch a 60-item batch under a producer-fast shape.
let batch: Vec<LineItem> = (0..60u32)
    .map(|id| LineItem::new(&id.to_le_bytes()).unwrap())
    .collect();
let variant = dispatcher.dispatch_batch(
    WorkloadShape::producer_fast(60),
    &batch,
)?;
assert_eq!(variant, DequeVariant::Khl);

// Or just inspect the routing decision without dispatching.
let picked = DequeDispatcher::pick(WorkloadShape::fan_out(4, 64));
assert_eq!(picked, DequeVariant::Urd);

See also