feat: channel benchmark
Harness for benchmarking channel performance, so we can understand exactly where bottlenecks are in our runtime.
This commit is contained in:
parent
85c7c557ee
commit
f947d6f8ec
4 changed files with 733 additions and 2 deletions
|
|
@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
default = ["getrandom"]
|
default = ["getrandom"]
|
||||||
getrandom = ["dep:getrandom"]
|
getrandom = ["dep:getrandom"]
|
||||||
no_random = [] # compile without access to a source of randomness
|
no_random = [] # compile without access to a source of randomness
|
||||||
|
bench = [] # expose internals for benchmarking
|
||||||
python = ["dep:pyo3"]
|
python = ["dep:pyo3"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
@ -25,3 +26,8 @@ criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
[[bench]]
|
[[bench]]
|
||||||
name = "runtime_benchmarks"
|
name = "runtime_benchmarks"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "channel_benchmarks"
|
||||||
|
harness = false
|
||||||
|
required-features = ["bench"]
|
||||||
|
|
|
||||||
722
benches/channel_benchmarks.rs
Normal file
722
benches/channel_benchmarks.rs
Normal file
|
|
@ -0,0 +1,722 @@
|
||||||
|
use criterion::{
|
||||||
|
criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput,
|
||||||
|
};
|
||||||
|
use std::sync::{Arc, Barrier};
|
||||||
|
use std::thread;
|
||||||
|
use swactor::channel::{HybridChannel, Receiver, Sender};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Payload types
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Tiny(u64);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Medium([u8; 256]);
|
||||||
|
|
||||||
|
impl Default for Medium {
|
||||||
|
fn default() -> Self {
|
||||||
|
Medium([0xAB; 256])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Large(Box<[u8; 4096]>);
|
||||||
|
|
||||||
|
impl Default for Large {
|
||||||
|
fn default() -> Self {
|
||||||
|
Large(Box::new([0xCD; 4096]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 1. SINGLE-THREADED — raw HybridChannel push/pop
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn st_push_pop(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("st_push_pop");
|
||||||
|
|
||||||
|
// 1a — Single push + pop (hot path: ring only)
|
||||||
|
for cap in [64, 256, 1024] {
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("single_roundtrip", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
let ch = HybridChannel::new(cap);
|
||||||
|
b.iter(|| {
|
||||||
|
ch.push(Tiny(42)).unwrap();
|
||||||
|
ch.pop().unwrap();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1b — Burst: push N then pop N (all in ring)
|
||||||
|
for n in [64, 256, 1024] {
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(BenchmarkId::new("burst_ring_only", n), &n, |b, &n| {
|
||||||
|
let ch = HybridChannel::new(n);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1c — Burst: push N into capacity N/2 (forces overflow)
|
||||||
|
for n in [128, 512, 2048] {
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("burst_with_overflow", n),
|
||||||
|
&n,
|
||||||
|
|b, &n| {
|
||||||
|
let ch = HybridChannel::new(n / 2);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1d — Burst: 100% overflow (capacity=1)
|
||||||
|
for n in [256, 1024] {
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("burst_all_overflow", n),
|
||||||
|
&n,
|
||||||
|
|b, &n| {
|
||||||
|
let ch = HybridChannel::new(1);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 2. EMPTY-POP COST
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn empty_pop(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("empty_pop");
|
||||||
|
|
||||||
|
group.bench_function("empty_channel", |b| {
|
||||||
|
let ch: HybridChannel<Tiny> = HybridChannel::new(64);
|
||||||
|
b.iter(|| {
|
||||||
|
assert!(ch.pop().is_none());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pop from a channel that previously had items (ring drained, overflow drained)
|
||||||
|
group.bench_function("after_drain", |b| {
|
||||||
|
let ch = HybridChannel::new(64);
|
||||||
|
for i in 0..100u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
while ch.pop().is_some() {}
|
||||||
|
b.iter(|| {
|
||||||
|
assert!(ch.pop().is_none());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 3. PAYLOAD SIZE COMPARISON
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn payload_sizes(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("payload_size");
|
||||||
|
let n = 512usize;
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
|
||||||
|
// Tiny (8 bytes)
|
||||||
|
group.bench_function("tiny_8B", |b| {
|
||||||
|
let ch = HybridChannel::new(n);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Medium (256 bytes)
|
||||||
|
group.bench_function("medium_256B", |b| {
|
||||||
|
let ch = HybridChannel::new(n);
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.push(Medium::default()).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Large (4096 bytes, boxed)
|
||||||
|
group.bench_function("large_4KB_boxed", |b| {
|
||||||
|
let ch = HybridChannel::new(n);
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.push(Large::default()).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Box<dyn Any + Send> — what the runtime actually uses
|
||||||
|
group.bench_function("boxed_any", |b| {
|
||||||
|
let ch: HybridChannel<Box<dyn std::any::Any + Send>> = HybridChannel::new(n);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
let _ = ch.push(Box::new(Tiny(i)));
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 4. SENDER / RECEIVER API
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn sender_receiver_api(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("sender_receiver");
|
||||||
|
|
||||||
|
// 4a — Single send + recv
|
||||||
|
group.bench_function("single_roundtrip", |b| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(64);
|
||||||
|
let tx = rx.new_sender();
|
||||||
|
b.iter(|| {
|
||||||
|
tx.try_send(Tiny(42)).unwrap();
|
||||||
|
rx.try_recv().unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 4b — Burst through sender/receiver
|
||||||
|
for n in [256, 1024, 4096] {
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(BenchmarkId::new("burst", n), &n, |b, &n| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(n);
|
||||||
|
let tx = rx.new_sender();
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
tx.try_send(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
rx.try_recv().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4c — Multiple senders (clone cost + contention-free since ST)
|
||||||
|
group.bench_function("clone_sender", |b| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(64);
|
||||||
|
let tx = rx.new_sender();
|
||||||
|
b.iter(|| {
|
||||||
|
let _tx2 = rx.new_sender();
|
||||||
|
tx.try_send(Tiny(1)).unwrap();
|
||||||
|
rx.try_recv().unwrap();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 5. SPSC — one producer thread, one consumer thread
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn spsc(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("spsc");
|
||||||
|
|
||||||
|
for n in [1_000usize, 10_000, 100_000] {
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(BenchmarkId::new("throughput", n), &n, |b, &n| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(1024);
|
||||||
|
let tx = rx.new_sender();
|
||||||
|
(tx, rx)
|
||||||
|
},
|
||||||
|
|(tx, rx)| {
|
||||||
|
let barrier = Arc::new(Barrier::new(2));
|
||||||
|
|
||||||
|
let b1 = barrier.clone();
|
||||||
|
let producer = thread::spawn(move || {
|
||||||
|
b1.wait();
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
tx.try_send(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let b2 = barrier.clone();
|
||||||
|
let consumer = thread::spawn(move || {
|
||||||
|
b2.wait();
|
||||||
|
let mut received = 0u64;
|
||||||
|
while received < n as u64 {
|
||||||
|
if rx.try_recv().is_some() {
|
||||||
|
received += 1;
|
||||||
|
} else {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
received
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.join().unwrap();
|
||||||
|
let count = consumer.join().unwrap();
|
||||||
|
assert_eq!(count, n as u64);
|
||||||
|
},
|
||||||
|
BatchSize::PerIteration,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPSC with varying ring capacities (fixed message count)
|
||||||
|
let n = 10_000usize;
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
for cap in [16, 64, 256, 1024, 8192] {
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("cap_sweep", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(cap);
|
||||||
|
let tx = rx.new_sender();
|
||||||
|
(tx, rx)
|
||||||
|
},
|
||||||
|
|(tx, rx)| {
|
||||||
|
let barrier = Arc::new(Barrier::new(2));
|
||||||
|
|
||||||
|
let b1 = barrier.clone();
|
||||||
|
let producer = thread::spawn(move || {
|
||||||
|
b1.wait();
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
tx.try_send(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let b2 = barrier.clone();
|
||||||
|
let consumer = thread::spawn(move || {
|
||||||
|
b2.wait();
|
||||||
|
let mut received = 0u64;
|
||||||
|
while received < n as u64 {
|
||||||
|
if rx.try_recv().is_some() {
|
||||||
|
received += 1;
|
||||||
|
} else {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
producer.join().unwrap();
|
||||||
|
consumer.join().unwrap();
|
||||||
|
},
|
||||||
|
BatchSize::PerIteration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 6. MPSC — multiple producer threads, one consumer thread
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn mpsc(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("mpsc");
|
||||||
|
|
||||||
|
// Vary number of producers
|
||||||
|
let msgs_per_producer = 5_000usize;
|
||||||
|
for num_producers in [2, 4, 8] {
|
||||||
|
let total = num_producers * msgs_per_producer;
|
||||||
|
group.throughput(Throughput::Elements(total as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("producers", num_producers),
|
||||||
|
&num_producers,
|
||||||
|
|b, &num_producers| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(1024);
|
||||||
|
let senders: Vec<Sender<Tiny>> =
|
||||||
|
(0..num_producers).map(|_| rx.new_sender()).collect();
|
||||||
|
(senders, rx)
|
||||||
|
},
|
||||||
|
|(senders, rx)| {
|
||||||
|
let barrier = Arc::new(Barrier::new(num_producers + 1));
|
||||||
|
|
||||||
|
let handles: Vec<_> = senders
|
||||||
|
.into_iter()
|
||||||
|
.map(|tx| {
|
||||||
|
let b = barrier.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
b.wait();
|
||||||
|
for i in 0..msgs_per_producer as u64 {
|
||||||
|
tx.try_send(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let total = num_producers * msgs_per_producer;
|
||||||
|
let b = barrier.clone();
|
||||||
|
let consumer = thread::spawn(move || {
|
||||||
|
b.wait();
|
||||||
|
let mut received = 0usize;
|
||||||
|
while received < total {
|
||||||
|
if rx.try_recv().is_some() {
|
||||||
|
received += 1;
|
||||||
|
} else {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
received
|
||||||
|
});
|
||||||
|
|
||||||
|
for h in handles {
|
||||||
|
h.join().unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(consumer.join().unwrap(), total);
|
||||||
|
},
|
||||||
|
BatchSize::PerIteration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heavy contention: many producers, small ring
|
||||||
|
for cap in [16, 256] {
|
||||||
|
let num_producers = 8;
|
||||||
|
let total = num_producers * msgs_per_producer;
|
||||||
|
group.throughput(Throughput::Elements(total as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("contention_cap", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(cap);
|
||||||
|
let senders: Vec<Sender<Tiny>> =
|
||||||
|
(0..num_producers).map(|_| rx.new_sender()).collect();
|
||||||
|
(senders, rx)
|
||||||
|
},
|
||||||
|
|(senders, rx)| {
|
||||||
|
let barrier = Arc::new(Barrier::new(num_producers + 1));
|
||||||
|
|
||||||
|
let handles: Vec<_> = senders
|
||||||
|
.into_iter()
|
||||||
|
.map(|tx| {
|
||||||
|
let b = barrier.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
b.wait();
|
||||||
|
for i in 0..msgs_per_producer as u64 {
|
||||||
|
tx.try_send(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let total = num_producers * msgs_per_producer;
|
||||||
|
let b = barrier.clone();
|
||||||
|
let consumer = thread::spawn(move || {
|
||||||
|
b.wait();
|
||||||
|
let mut received = 0usize;
|
||||||
|
while received < total {
|
||||||
|
if rx.try_recv().is_some() {
|
||||||
|
received += 1;
|
||||||
|
} else {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
received
|
||||||
|
});
|
||||||
|
|
||||||
|
for h in handles {
|
||||||
|
h.join().unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(consumer.join().unwrap(), total);
|
||||||
|
},
|
||||||
|
BatchSize::PerIteration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 7. OVERFLOW RATIO — measure how overflow % affects throughput
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn overflow_ratio(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("overflow_ratio");
|
||||||
|
let n = 4096usize;
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
|
||||||
|
// 0% overflow
|
||||||
|
group.bench_function("0pct", |b| {
|
||||||
|
let ch = HybridChannel::new(n);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 25% overflow
|
||||||
|
group.bench_function("25pct", |b| {
|
||||||
|
let ch = HybridChannel::new(n * 3 / 4);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 50% overflow
|
||||||
|
group.bench_function("50pct", |b| {
|
||||||
|
let ch = HybridChannel::new(n / 2);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 75% overflow
|
||||||
|
group.bench_function("75pct", |b| {
|
||||||
|
let ch = HybridChannel::new(n / 4);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 100% overflow
|
||||||
|
group.bench_function("100pct", |b| {
|
||||||
|
let ch = HybridChannel::new(1);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..n {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 8. INTERLEAVED — alternating push/pop (simulates steady-state runtime)
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn interleaved(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("interleaved");
|
||||||
|
|
||||||
|
// Push 1, pop 1 — the most common runtime pattern
|
||||||
|
for cap in [64, 256, 1024] {
|
||||||
|
let n = 4096usize;
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("push1_pop1", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
let ch = HybridChannel::new(cap);
|
||||||
|
b.iter(|| {
|
||||||
|
for i in 0..n as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push K, pop K — batched steady-state
|
||||||
|
for k in [4, 16, 64] {
|
||||||
|
let rounds = 256usize;
|
||||||
|
let n = rounds * k;
|
||||||
|
group.throughput(Throughput::Elements(n as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("batch_k", k),
|
||||||
|
&k,
|
||||||
|
|b, &k| {
|
||||||
|
let ch = HybridChannel::new(256);
|
||||||
|
b.iter(|| {
|
||||||
|
for _ in 0..rounds {
|
||||||
|
for i in 0..k as u64 {
|
||||||
|
ch.push(Tiny(i)).unwrap();
|
||||||
|
}
|
||||||
|
for _ in 0..k {
|
||||||
|
ch.pop().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 9. CHANNEL CREATION COST
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn creation(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("creation");
|
||||||
|
|
||||||
|
for cap in [16, 64, 256, 1024, 4096] {
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("hybrid_channel", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
b.iter(|| HybridChannel::<Tiny>::new(cap));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for cap in [16, 64, 256, 1024, 4096] {
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("receiver_and_sender", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
b.iter(|| {
|
||||||
|
let rx: Receiver<Tiny> = Receiver::new(cap);
|
||||||
|
let _tx = rx.new_sender();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// 10. SPSC PINGPONG — latency measurement (two channels, bounce messages)
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
fn spsc_pingpong(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("spsc_pingpong");
|
||||||
|
|
||||||
|
for cap in [64, 1024] {
|
||||||
|
let rounds = 10_000usize;
|
||||||
|
group.throughput(Throughput::Elements(rounds as u64));
|
||||||
|
group.bench_with_input(
|
||||||
|
BenchmarkId::new("roundtrips", cap),
|
||||||
|
&cap,
|
||||||
|
|b, &cap| {
|
||||||
|
b.iter_batched(
|
||||||
|
|| {
|
||||||
|
// Channel A: thread1 → thread2
|
||||||
|
let rx_a: Receiver<Tiny> = Receiver::new(cap);
|
||||||
|
let tx_a = rx_a.new_sender();
|
||||||
|
// Channel B: thread2 → thread1
|
||||||
|
let rx_b: Receiver<Tiny> = Receiver::new(cap);
|
||||||
|
let tx_b = rx_b.new_sender();
|
||||||
|
(tx_a, rx_a, tx_b, rx_b)
|
||||||
|
},
|
||||||
|
|(tx_a, rx_a, tx_b, rx_b)| {
|
||||||
|
let barrier = Arc::new(Barrier::new(2));
|
||||||
|
|
||||||
|
let b1 = barrier.clone();
|
||||||
|
let t1 = thread::spawn(move || {
|
||||||
|
b1.wait();
|
||||||
|
for i in 0..rounds as u64 {
|
||||||
|
tx_a.try_send(Tiny(i)).unwrap();
|
||||||
|
while rx_b.try_recv().is_none() {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let b2 = barrier.clone();
|
||||||
|
let t2 = thread::spawn(move || {
|
||||||
|
b2.wait();
|
||||||
|
for _ in 0..rounds {
|
||||||
|
while rx_a.try_recv().is_none() {
|
||||||
|
std::hint::spin_loop();
|
||||||
|
}
|
||||||
|
tx_b.try_send(Tiny(0)).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
t1.join().unwrap();
|
||||||
|
t2.join().unwrap();
|
||||||
|
},
|
||||||
|
BatchSize::PerIteration,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// Register all groups
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
criterion_group!(
|
||||||
|
benches,
|
||||||
|
st_push_pop,
|
||||||
|
empty_pop,
|
||||||
|
payload_sizes,
|
||||||
|
sender_receiver_api,
|
||||||
|
spsc,
|
||||||
|
mpsc,
|
||||||
|
overflow_ratio,
|
||||||
|
interleaved,
|
||||||
|
creation,
|
||||||
|
spsc_pingpong,
|
||||||
|
);
|
||||||
|
criterion_main!(benches);
|
||||||
|
|
@ -41,7 +41,7 @@ impl<T> HybridChannel<T> {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Receiver<T> {
|
pub struct Receiver<T> {
|
||||||
queue: Arc<HybridChannel<T>>,
|
queue: Arc<HybridChannel<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ impl<T> Receiver<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct Sender<T> {
|
pub struct Sender<T> {
|
||||||
queue: Arc<HybridChannel<T>>,
|
queue: Arc<HybridChannel<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
pub mod actor;
|
pub mod actor;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
||||||
|
#[cfg(feature = "bench")]
|
||||||
|
pub mod channel;
|
||||||
|
#[cfg(not(feature = "bench"))]
|
||||||
pub(crate) mod channel;
|
pub(crate) mod channel;
|
||||||
pub(crate) mod error;
|
pub(crate) mod error;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue