use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;
use std::time::Instant;
use subetha_cxc::adaptive_ring::{AdaptiveRing, RingShape};
use subetha_cxc::capacity_adaptive_ring::CapacityAdaptiveRing;
use subetha_cxc::capacity_broadcast_ring::CapacityBroadcastRing;
use subetha_cxc::capacity_pubsub_ring::CapacityPubSubRing;
use subetha_cxc::locale_adaptive_ring::{Locale, LocaleAdaptiveRing};
use subetha_cxc::mpmc_ring::SharedRingMpmc;
use subetha_cxc::mpsc_ring::{SharedRingMpsc, SharedRingMpscFifo};
use subetha_cxc::protocol_pubsub::{PubSubReadError, PubSubRing};
use subetha_cxc::shared_broadcast_ring::SharedBroadcastRing;
use subetha_cxc::shared_ring::{SharedRing, SharedRingSpsc};
fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() != 7 {
eprintln!(
"usage: {} <primitive> <locale> <capacity> <n_producers> <n_consumers> <n_items>",
args[0]
);
std::process::exit(2);
}
let primitive = args[1].as_str();
let locale = args[2].as_str();
let capacity: usize = args[3].parse().expect("capacity must be usize");
let n_producers: usize = args[4].parse().expect("n_producers must be usize");
let n_consumers: usize = args[5].parse().expect("n_consumers must be usize");
let n_items: u64 = args[6].parse().expect("n_items must be u64");
let result = match primitive {
"spsc" => bench_spsc(locale, capacity, n_items),
"mpsc" => bench_mpsc_composed(locale, capacity, n_producers, n_items),
"mpsc-fifo" => bench_mpsc_fifo(locale, capacity, n_producers, n_items),
"mpmc" => bench_mpmc_composed(locale, capacity, n_producers, n_consumers, n_items),
"vyukov" => bench_vyukov(locale, capacity, n_producers, n_consumers, n_items),
"broadcast" => bench_broadcast(locale, capacity, n_consumers, n_items),
"pubsub" => bench_pubsub(locale, capacity, n_consumers, n_items),
"adaptive-spsc" => bench_adaptive(locale, capacity, RingShape::Spsc, 1, 1, n_items),
"adaptive-mpsc" => bench_adaptive(locale, capacity, RingShape::Mpsc, n_producers, 1, n_items),
"adaptive-mpmc" => {
bench_adaptive(locale, capacity, RingShape::Mpmc, n_producers, n_consumers, n_items)
}
"adaptive-vyukov" => {
bench_adaptive(locale, capacity, RingShape::Vyukov, n_producers, n_consumers, n_items)
}
"adaptive-pinned-spsc" => bench_adaptive_pinned(locale, capacity, RingShape::Spsc, 1, 1, n_items),
"adaptive-pinned-mpsc" => bench_adaptive_pinned(locale, capacity, RingShape::Mpsc, n_producers, 1, n_items),
"adaptive-pinned-mpmc" => bench_adaptive_pinned(locale, capacity, RingShape::Mpmc, n_producers, n_consumers, n_items),
"adaptive-pinned-vyukov" => bench_adaptive_pinned(locale, capacity, RingShape::Vyukov, n_producers, n_consumers, n_items),
"locale-adaptive" => bench_locale_adaptive(locale, capacity, n_producers, n_consumers, n_items),
"capacity-spsc" => {
bench_capacity_adaptive(locale, capacity, RingShape::Spsc, 1, 1, n_items)
}
"capacity-mpsc" => {
bench_capacity_adaptive(locale, capacity, RingShape::Mpsc, n_producers, 1, n_items)
}
"capacity-mpmc" => bench_capacity_adaptive(
locale, capacity, RingShape::Mpmc, n_producers, n_consumers, n_items,
),
"capacity-vyukov" => bench_capacity_adaptive(
locale, capacity, RingShape::Vyukov, n_producers, n_consumers, n_items,
),
"capacity-pinned-spsc" => bench_capacity_pinned(locale, capacity, RingShape::Spsc, 1, 1, n_items),
"capacity-pinned-mpsc" => bench_capacity_pinned(locale, capacity, RingShape::Mpsc, n_producers, 1, n_items),
"capacity-pinned-mpmc" => bench_capacity_pinned(locale, capacity, RingShape::Mpmc, n_producers, n_consumers, n_items),
"capacity-pinned-vyukov" => bench_capacity_pinned(locale, capacity, RingShape::Vyukov, n_producers, n_consumers, n_items),
"capacity-broadcast" => bench_capacity_broadcast(locale, capacity, n_consumers, n_items),
"capacity-pinned-broadcast" => bench_capacity_broadcast_pinned(locale, capacity, n_consumers, n_items),
"capacity-pubsub" => bench_capacity_pubsub(locale, capacity, n_consumers, n_items),
"crossbeam" => bench_crossbeam(capacity, n_producers, n_consumers, n_items),
"std-mpsc" => bench_std_mpsc(capacity, n_producers, n_items),
other => {
eprintln!("unknown primitive: {other}");
std::process::exit(2);
}
};
emit_result(primitive, locale, capacity, n_producers, n_consumers, n_items, result);
}
enum BenchResult {
Ok {
elapsed_ms: f64,
throughput_m_per_s: f64,
eff_p: usize,
eff_c: usize,
eff_n: u64,
},
Skip(String),
}
fn emit_result(
primitive: &str,
locale: &str,
cap: usize,
req_p: usize,
req_c: usize,
req_n: u64,
r: BenchResult,
) {
match r {
BenchResult::Ok {
elapsed_ms,
throughput_m_per_s,
eff_p,
eff_c,
eff_n,
} => {
println!(
"primitive={primitive} locale={locale} cap={cap} reqP={req_p} reqC={req_c} reqN={req_n} effP={eff_p} effC={eff_c} effN={eff_n} elapsed_ms={elapsed_ms:.3} throughput_M_per_s={throughput_m_per_s:.3}"
);
}
BenchResult::Skip(reason) => {
println!(
"primitive={primitive} locale={locale} cap={cap} reqP={req_p} reqC={req_c} reqN={req_n} SKIP={reason}"
);
}
}
}
fn bench_result_from(t0: Instant, eff_n: u64, eff_p: usize, eff_c: usize) -> BenchResult {
let elapsed = t0.elapsed();
BenchResult::Ok {
elapsed_ms: elapsed.as_secs_f64() * 1000.0,
throughput_m_per_s: eff_n as f64 / elapsed.as_secs_f64() / 1_000_000.0,
eff_p,
eff_c,
eff_n,
}
}
fn bench_spsc(locale: &str, capacity: usize, n_items: u64) -> BenchResult {
if locale != "anon" {
return BenchResult::Skip(format!("locale-{locale}-unsupported"));
}
let (producer, consumer) = match SharedRingSpsc::create_anon_pair(capacity) {
Ok(pc) => pc,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
};
let t0 = Instant::now();
let producer_h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..n_items {
while producer.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
let consumer_h = thread::spawn(move || {
let mut buf = [0u8; 64];
for _ in 0..n_items {
while consumer.try_pop(&mut buf).is_err() {
std::hint::spin_loop();
}
}
});
producer_h.join().unwrap();
consumer_h.join().unwrap();
bench_result_from(t0, n_items, 1, 1)
}
fn bench_mpsc_composed(
locale: &str,
capacity: usize,
n_producers: usize,
n_items: u64,
) -> BenchResult {
if locale != "anon" {
return BenchResult::Skip(format!("locale-{locale}-unsupported"));
}
let (producers, consumer) = match SharedRingMpsc::create_anon_pool(n_producers, capacity) {
Ok(pc) => pc,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
};
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for prod in producers {
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while prod.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut buf = [0u8; 64];
let mut drained: u64 = 0;
while drained < total {
if consumer.try_pop(&mut buf).is_ok() {
drained += 1;
} else {
std::hint::spin_loop();
}
}
for h in producer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, 1)
}
fn bench_mpsc_fifo(
locale: &str,
capacity: usize,
n_producers: usize,
n_items: u64,
) -> BenchResult {
if locale != "anon" {
return BenchResult::Skip(format!("locale-{locale}-unsupported"));
}
let (producers, consumer) = match SharedRingMpscFifo::create_anon_pool(n_producers, capacity) {
Ok(pc) => pc,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
};
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for prod in producers {
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while prod.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut buf = [0u8; 64];
let mut drained: u64 = 0;
while drained < total {
if consumer.try_pop(&mut buf).is_ok() {
drained += 1;
} else {
std::hint::spin_loop();
}
}
for h in producer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, 1)
}
fn bench_mpmc_composed(
locale: &str,
capacity: usize,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
if locale != "anon" {
return BenchResult::Skip(format!("locale-{locale}-unsupported"));
}
if n_producers < n_consumers {
return BenchResult::Skip(format!("n_producers-{n_producers}-lt-n_consumers-{n_consumers}"));
}
let (producers, consumers) =
match SharedRingMpmc::create_anon_grid(n_producers, n_consumers, capacity) {
Ok(pc) => pc,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
};
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for prod in producers {
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while prod.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for cons in consumers {
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
loop {
if cons.try_pop(&mut buf).is_ok() {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_vyukov(
locale: &str,
capacity: usize,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let ring: Arc<SharedRing> = match locale {
"anon" => match SharedRing::create_anon(capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_vyukov_{}.bin", std::process::id()));
match SharedRing::create(&path, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_vyukov_{}", std::process::id());
let total_size = subetha_cxc::shared_ring::ring_file_size(capacity);
let shm = match subetha_cxc::shm_file::ShmFile::create_or_open_named(&name, total_size)
{
Ok(s) => s,
Err(e) => return BenchResult::Skip(format!("shm-create-failed-{e}")),
};
match SharedRing::create_from_shm(shm, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for _ in 0..n_producers {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while r.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for _ in 0..n_consumers {
let r = Arc::clone(&ring);
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
loop {
if r.try_pop(&mut buf).is_ok() {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_broadcast(
locale: &str,
capacity: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let ring: Arc<SharedBroadcastRing> = match locale {
"anon" => match SharedBroadcastRing::create_anon(capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_broadcast_{}.bin", std::process::id()));
match SharedBroadcastRing::create(&path, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_broadcast_{}", std::process::id());
let total_size = subetha_cxc::shared_broadcast_ring::broadcast_file_size(capacity);
let shm = match subetha_cxc::shm_file::ShmFile::create_or_open_named(&name, total_size)
{
Ok(s) => s,
Err(e) => return BenchResult::Skip(format!("shm-create-failed-{e}")),
};
match SharedBroadcastRing::create_from_shm(shm, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
let consumer_ids: Vec<usize> = match (0..n_consumers).map(|_| ring.register_consumer()).collect()
{
Ok(ids) => ids,
Err(e) => return BenchResult::Skip(format!("register-failed-{e:?}")),
};
let t0 = Instant::now();
let r = Arc::clone(&ring);
let producer_h = thread::spawn(move || {
let payload = [0u8; 52];
for _ in 0..n_items {
while r.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
let mut consumer_handles = Vec::new();
for cid in consumer_ids {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
for _ in 0..n_items {
while r.try_recv(cid, &mut buf).is_err() {
std::hint::spin_loop();
}
}
});
consumer_handles.push(h);
}
producer_h.join().unwrap();
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, n_items, 1, n_consumers)
}
fn bench_pubsub(
locale: &str,
capacity: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let ring: Arc<PubSubRing> = match locale {
"anon" => match PubSubRing::create_anon(capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_pubsub_{}.bin", std::process::id()));
match PubSubRing::create(&path, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e}")),
}
}
"shmfs" => {
let name = format!("bench_pubsub_{}", std::process::id());
let total_size = subetha_cxc::protocol_pubsub::pubsub_ring_file_size(capacity);
let shm = match subetha_cxc::shm_file::ShmFile::create_or_open_named(&name, total_size)
{
Ok(s) => s,
Err(e) => return BenchResult::Skip(format!("shm-create-failed-{e}")),
};
match PubSubRing::create_from_shm(shm, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
let sub_positions: Vec<Arc<AtomicU64>> = (0..n_consumers)
.map(|_| Arc::new(AtomicU64::new(0)))
.collect();
let t0 = Instant::now();
let r_prod = Arc::clone(&ring);
let sub_pos_for_prod: Vec<Arc<AtomicU64>> = sub_positions.to_vec();
let producer_h = thread::spawn(move || {
let payload = [0u8; 56];
for i in 0..n_items {
loop {
let min_sub = sub_pos_for_prod
.iter()
.map(|a| a.load(Ordering::Acquire))
.min()
.unwrap_or(0);
if i.saturating_sub(min_sub) < capacity as u64 {
break;
}
std::hint::spin_loop();
}
r_prod.publish(&payload);
}
});
let mut consumer_handles = Vec::new();
for pos_arc in &sub_positions {
let r = Arc::clone(&ring);
let pos = Arc::clone(pos_arc);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
let mut p: u64 = 0;
while p < n_items {
match r.read_at(p, &mut buf) {
Ok(()) => {
p += 1;
pos.store(p, Ordering::Release);
}
Err(PubSubReadError::Pending) => std::hint::spin_loop(),
Err(PubSubReadError::Lost) => {
eprintln!("pubsub bench: subscriber lost at p={p}");
return;
}
}
}
});
consumer_handles.push(h);
}
producer_h.join().unwrap();
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, n_items, 1, n_consumers)
}
fn bench_adaptive(
locale: &str,
capacity: usize,
shape: RingShape,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let max_p = n_producers.max(1);
let max_c = n_consumers.max(1);
let ring: Arc<AdaptiveRing> = match locale {
"anon" => match AdaptiveRing::create_anon(max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_adaptive_{}", std::process::id()));
match AdaptiveRing::create(&path, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_adaptive_{}", std::process::id());
match AdaptiveRing::create_shmfs(&name, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
for _ in 0..n_producers {
if let Err(e) = ring.register_producer() {
return BenchResult::Skip(format!("register-producer-{e:?}"));
}
}
for _ in 0..n_consumers {
if let Err(e) = ring.register_consumer() {
return BenchResult::Skip(format!("register-consumer-{e:?}"));
}
}
if shape != RingShape::Spsc
&& let Err(e) = ring.morph_to(shape)
{
return BenchResult::Skip(format!("morph-failed-{e:?}"));
}
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for pid in 0..n_producers {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while r.try_send(pid, &payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for cid in 0..n_consumers {
let r = Arc::clone(&ring);
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
if n_consumers == 1 {
for _ in 0..total {
while r.try_recv(cid, &mut buf).is_err() {
std::hint::spin_loop();
}
}
} else {
loop {
if r.try_recv(cid, &mut buf).is_ok() {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_locale_adaptive(
locale: &str,
capacity: usize,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let max_p = n_producers.max(1);
let max_c = n_consumers.max(1);
let starting_locale = match locale {
"anon" => Locale::Anon,
"file" => Locale::File,
"shmfs" => Locale::ShmFs,
other => return BenchResult::Skip(format!("locale-{other}-unknown")),
};
let path = std::env::temp_dir()
.join(format!("bench_locale_adaptive_{}", std::process::id()));
let ring = match LocaleAdaptiveRing::create(&path, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
};
if starting_locale != Locale::Anon
&& let Err(e) = ring.migrate_to(starting_locale)
{
return BenchResult::Skip(format!("migrate-locale-failed-{e:?}"));
}
for _ in 0..n_producers {
if let Err(e) = ring.register_producer() {
return BenchResult::Skip(format!("register-producer-{e:?}"));
}
}
for _ in 0..n_consumers {
if let Err(e) = ring.register_consumer() {
return BenchResult::Skip(format!("register-consumer-{e:?}"));
}
}
let target_shape = if n_producers > 1 && n_consumers > 1 {
RingShape::Mpmc
} else if n_producers > 1 {
RingShape::Mpsc
} else {
RingShape::Spsc
};
if target_shape != RingShape::Spsc {
let active_backing: &AdaptiveRing = match starting_locale {
Locale::Anon => ring.anon_ring(),
Locale::File => ring.file_ring(),
Locale::ShmFs => ring.shmfs_ring(),
};
if let Err(e) = active_backing.morph_to(target_shape) {
return BenchResult::Skip(format!("inner-shape-morph-{e:?}"));
}
}
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for pid in 0..n_producers {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while r.try_send(pid, &payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for cid in 0..n_consumers {
let r = Arc::clone(&ring);
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
if n_consumers == 1 {
for _ in 0..total {
while r.try_recv(cid, &mut buf).is_err() {
std::hint::spin_loop();
}
}
} else {
loop {
if r.try_recv(cid, &mut buf).is_ok() {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_capacity_adaptive(
locale: &str,
capacity: usize,
shape: RingShape,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let max_p = n_producers.max(1);
let max_c = n_consumers.max(1);
let ring: Arc<CapacityAdaptiveRing> = match locale {
"anon" => match CapacityAdaptiveRing::create_anon(max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_capadapt_{}.bin", std::process::id()));
match CapacityAdaptiveRing::create(&path, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_capadapt_{}", std::process::id());
match CapacityAdaptiveRing::create_shmfs(&name, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
for _ in 0..n_producers {
if let Err(e) = ring.register_producer() {
return BenchResult::Skip(format!("register-producer-{e:?}"));
}
}
for _ in 0..n_consumers {
if let Err(e) = ring.register_consumer() {
return BenchResult::Skip(format!("register-consumer-{e:?}"));
}
}
if shape != RingShape::Spsc
&& let Err(e) = ring.ring_handle().morph_to(shape)
{
return BenchResult::Skip(format!("morph-shape-{e:?}"));
}
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for pid in 0..n_producers {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
while r.try_send(pid, &payload).is_err() {
std::hint::spin_loop();
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for cid in 0..n_consumers {
let r = Arc::clone(&ring);
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
if n_consumers == 1 {
for _ in 0..total {
while r.try_recv(cid, &mut buf).is_err() {
std::hint::spin_loop();
}
}
} else {
loop {
if r.try_recv(cid, &mut buf).is_ok() {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_capacity_broadcast(
locale: &str,
capacity: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let ring: Arc<CapacityBroadcastRing> = match locale {
"anon" => match CapacityBroadcastRing::create_anon(capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_capbcast_{}.bin", std::process::id()));
match CapacityBroadcastRing::create(&path, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_capbcast_{}", std::process::id());
match CapacityBroadcastRing::create_shmfs(&name, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
let consumer_ids: Vec<usize> = match (0..n_consumers).map(|_| ring.register_consumer()).collect()
{
Ok(ids) => ids,
Err(e) => return BenchResult::Skip(format!("register-failed-{e:?}")),
};
let t0 = Instant::now();
let r = Arc::clone(&ring);
let producer_h = thread::spawn(move || {
let payload = [0u8; 52];
for _ in 0..n_items {
while r.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
let mut consumer_handles = Vec::new();
for cid in consumer_ids {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
for _ in 0..n_items {
while r.try_recv(cid, &mut buf).is_err() {
std::hint::spin_loop();
}
}
});
consumer_handles.push(h);
}
producer_h.join().unwrap();
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, n_items, 1, n_consumers)
}
fn bench_capacity_pubsub(
locale: &str,
capacity: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let ring: Arc<CapacityPubSubRing> = match locale {
"anon" => match CapacityPubSubRing::create_anon(capacity) {
Ok(r) => r,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_cappubsub_{}.bin", std::process::id()));
match CapacityPubSubRing::create(&path, capacity) {
Ok(r) => r,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_cappubsub_{}", std::process::id());
match CapacityPubSubRing::create_shmfs(&name, capacity) {
Ok(r) => r,
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
let subscribers: Vec<_> = (0..n_consumers)
.map(|_| ring.subscribe_from_oldest())
.collect();
let sub_positions: Vec<Arc<AtomicU64>> = (0..n_consumers)
.map(|_| Arc::new(AtomicU64::new(0)))
.collect();
let t0 = Instant::now();
let r_prod = Arc::clone(&ring);
let pos_for_prod: Vec<Arc<AtomicU64>> = sub_positions.to_vec();
let producer_h = thread::spawn(move || {
let payload = [0u8; 56];
for i in 0..n_items {
loop {
let min_sub = pos_for_prod
.iter()
.map(|a| a.load(Ordering::Acquire))
.min()
.unwrap_or(0);
if i.saturating_sub(min_sub) < capacity as u64 {
break;
}
std::hint::spin_loop();
}
r_prod.publish(&payload);
}
});
let mut consumer_handles = Vec::new();
for (sub_i, mut sub) in subscribers.into_iter().enumerate() {
let pos = Arc::clone(&sub_positions[sub_i]);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
let mut count: u64 = 0;
while count < n_items {
match sub.try_next(&mut buf) {
Ok(()) => {
count += 1;
pos.store(count, Ordering::Release);
}
Err(PubSubReadError::Pending) => std::hint::spin_loop(),
Err(PubSubReadError::Lost) => {
eprintln!("cap-pubsub bench: subscriber lost at count={count}");
return;
}
}
}
});
consumer_handles.push(h);
}
producer_h.join().unwrap();
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, n_items, 1, n_consumers)
}
fn bench_crossbeam(
capacity: usize,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let (tx, rx) = crossbeam_channel::bounded::<[u8; 56]>(capacity);
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for _ in 0..n_producers {
let tx = tx.clone();
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
tx.send(payload).expect("crossbeam send");
}
});
producer_handles.push(h);
}
drop(tx);
let mut consumer_handles = Vec::new();
for _ in 0..n_consumers {
let rx = rx.clone();
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
while rx.recv().is_ok() {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_std_mpsc(capacity: usize, n_producers: usize, n_items: u64) -> BenchResult {
let (tx, rx) = std::sync::mpsc::sync_channel::<[u8; 56]>(capacity);
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for _ in 0..n_producers {
let tx = tx.clone();
let h = thread::spawn(move || {
let payload = [0u8; 56];
for _ in 0..items_per_producer {
tx.send(payload).expect("std mpsc send");
}
});
producer_handles.push(h);
}
drop(tx);
let consumer_h = thread::spawn(move || {
for _ in 0..total {
rx.recv().expect("std mpsc recv");
}
});
for h in producer_handles {
h.join().unwrap();
}
consumer_h.join().unwrap();
bench_result_from(t0, total, n_producers, 1)
}
fn bench_adaptive_pinned(
locale: &str,
capacity: usize,
shape: RingShape,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let max_p = n_producers.max(1);
let max_c = n_consumers.max(1);
let ring: Arc<AdaptiveRing> = match locale {
"anon" => match AdaptiveRing::create_anon(max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_adaptive_pinned_{}", std::process::id()));
match AdaptiveRing::create(&path, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_adaptive_pinned_{}", std::process::id());
match AdaptiveRing::create_shmfs(&name, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
for _ in 0..n_producers {
if let Err(e) = ring.register_producer() {
return BenchResult::Skip(format!("register-producer-{e:?}"));
}
}
for _ in 0..n_consumers {
if let Err(e) = ring.register_consumer() {
return BenchResult::Skip(format!("register-consumer-{e:?}"));
}
}
if shape != RingShape::Spsc
&& let Err(e) = ring.morph_to(shape)
{
return BenchResult::Skip(format!("morph-failed-{e:?}"));
}
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for pid in 0..n_producers {
let r = Arc::clone(&ring);
let h = thread::spawn(move || {
let pin = r.pin_current_shape();
let payload = [0u8; 56];
for _ in 0..items_per_producer {
match shape {
RingShape::Spsc => while pin.spsc_try_push(&payload).is_err() { std::hint::spin_loop(); }
RingShape::Mpsc => while pin.mpsc_try_push(pid, &payload).is_err() { std::hint::spin_loop(); }
RingShape::Mpmc => while pin.mpmc_try_push(pid, &payload).is_err() { std::hint::spin_loop(); }
RingShape::Vyukov => while pin.vyukov_try_push(&payload).is_err() { std::hint::spin_loop(); }
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for cid in 0..n_consumers {
let r = Arc::clone(&ring);
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let pin = r.pin_current_shape();
let mut buf = [0u8; 64];
if n_consumers == 1 {
for _ in 0..total {
loop {
let ok = match shape {
RingShape::Spsc => pin.spsc_try_pop(&mut buf).is_ok(),
RingShape::Mpsc => pin.mpsc_try_pop(&mut buf).is_ok(),
RingShape::Mpmc => pin.mpmc_try_pop(cid, &mut buf).is_ok(),
RingShape::Vyukov => pin.vyukov_try_pop(&mut buf).is_ok(),
};
if ok { break; }
std::hint::spin_loop();
}
}
} else {
loop {
let ok = match shape {
RingShape::Spsc => pin.spsc_try_pop(&mut buf).is_ok(),
RingShape::Mpsc => pin.mpsc_try_pop(&mut buf).is_ok(),
RingShape::Mpmc => pin.mpmc_try_pop(cid, &mut buf).is_ok(),
RingShape::Vyukov => pin.vyukov_try_pop(&mut buf).is_ok(),
};
if ok {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_capacity_pinned(
locale: &str,
capacity: usize,
shape: RingShape,
n_producers: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let max_p = n_producers.max(1);
let max_c = n_consumers.max(1);
let cap_ring: Arc<CapacityAdaptiveRing> = match locale {
"anon" => match CapacityAdaptiveRing::create_anon(max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_cap_pinned_{}.bin", std::process::id()));
match CapacityAdaptiveRing::create(&path, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_cap_pinned_{}", std::process::id());
match CapacityAdaptiveRing::create_shmfs(&name, max_p, max_c, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
for _ in 0..n_producers {
if let Err(e) = cap_ring.register_producer() {
return BenchResult::Skip(format!("register-producer-{e:?}"));
}
}
for _ in 0..n_consumers {
if let Err(e) = cap_ring.register_consumer() {
return BenchResult::Skip(format!("register-consumer-{e:?}"));
}
}
if shape != RingShape::Spsc
&& let Err(e) = cap_ring.ring_handle().morph_to(shape)
{
return BenchResult::Skip(format!("morph-shape-{e:?}"));
}
let inner: Arc<AdaptiveRing> = cap_ring.ring_handle();
let items_per_producer = n_items / n_producers as u64;
let total = items_per_producer * n_producers as u64;
let drained = Arc::new(AtomicU64::new(0));
let t0 = Instant::now();
let mut producer_handles = Vec::new();
for pid in 0..n_producers {
let r = Arc::clone(&inner);
let h = thread::spawn(move || {
let pin = r.pin_current_shape();
let payload = [0u8; 56];
for _ in 0..items_per_producer {
match shape {
RingShape::Spsc => while pin.spsc_try_push(&payload).is_err() { std::hint::spin_loop(); }
RingShape::Mpsc => while pin.mpsc_try_push(pid, &payload).is_err() { std::hint::spin_loop(); }
RingShape::Mpmc => while pin.mpmc_try_push(pid, &payload).is_err() { std::hint::spin_loop(); }
RingShape::Vyukov => while pin.vyukov_try_push(&payload).is_err() { std::hint::spin_loop(); }
}
}
});
producer_handles.push(h);
}
let mut consumer_handles = Vec::new();
for cid in 0..n_consumers {
let r = Arc::clone(&inner);
let d = Arc::clone(&drained);
let h = thread::spawn(move || {
let pin = r.pin_current_shape();
let mut buf = [0u8; 64];
if n_consumers == 1 {
for _ in 0..total {
loop {
let ok = match shape {
RingShape::Spsc => pin.spsc_try_pop(&mut buf).is_ok(),
RingShape::Mpsc => pin.mpsc_try_pop(&mut buf).is_ok(),
RingShape::Mpmc => pin.mpmc_try_pop(cid, &mut buf).is_ok(),
RingShape::Vyukov => pin.vyukov_try_pop(&mut buf).is_ok(),
};
if ok { break; }
std::hint::spin_loop();
}
}
} else {
loop {
let ok = match shape {
RingShape::Spsc => pin.spsc_try_pop(&mut buf).is_ok(),
RingShape::Mpsc => pin.mpsc_try_pop(&mut buf).is_ok(),
RingShape::Mpmc => pin.mpmc_try_pop(cid, &mut buf).is_ok(),
RingShape::Vyukov => pin.vyukov_try_pop(&mut buf).is_ok(),
};
if ok {
if d.fetch_add(1, Ordering::Relaxed) + 1 >= total {
return;
}
} else if d.load(Ordering::Relaxed) >= total {
return;
} else {
std::hint::spin_loop();
}
}
}
});
consumer_handles.push(h);
}
for h in producer_handles {
h.join().unwrap();
}
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, total, n_producers, n_consumers)
}
fn bench_capacity_broadcast_pinned(
locale: &str,
capacity: usize,
n_consumers: usize,
n_items: u64,
) -> BenchResult {
let cap_ring: Arc<CapacityBroadcastRing> = match locale {
"anon" => match CapacityBroadcastRing::create_anon(capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
},
"file" => {
let path = std::env::temp_dir()
.join(format!("bench_capbcast_pinned_{}.bin", std::process::id()));
match CapacityBroadcastRing::create(&path, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
"shmfs" => {
let name = format!("bench_capbcast_pinned_{}", std::process::id());
match CapacityBroadcastRing::create_shmfs(&name, capacity) {
Ok(r) => Arc::new(r),
Err(e) => return BenchResult::Skip(format!("create-failed-{e:?}")),
}
}
_ => return BenchResult::Skip(format!("locale-{locale}-unknown")),
};
let consumer_ids: Vec<usize> = match (0..n_consumers).map(|_| cap_ring.register_consumer()).collect()
{
Ok(ids) => ids,
Err(e) => return BenchResult::Skip(format!("register-failed-{e:?}")),
};
let inner = cap_ring.ring_handle();
let t0 = Instant::now();
let r = Arc::clone(&inner);
let producer_h = thread::spawn(move || {
let payload = [0u8; 52];
for _ in 0..n_items {
while r.try_push(&payload).is_err() {
std::hint::spin_loop();
}
}
});
let mut consumer_handles = Vec::new();
for cid in consumer_ids {
let r = Arc::clone(&inner);
let h = thread::spawn(move || {
let mut buf = [0u8; 64];
for _ in 0..n_items {
while r.try_recv(cid, &mut buf).is_err() {
std::hint::spin_loop();
}
}
});
consumer_handles.push(h);
}
producer_h.join().unwrap();
for h in consumer_handles {
h.join().unwrap();
}
bench_result_from(t0, n_items, 1, n_consumers)
}