feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
//! Core throughput benchmarks for the swactor runtime.
|
|
|
|
|
//!
|
|
|
|
|
//! These benchmarks measure:
|
|
|
|
|
//! - Message passing throughput
|
|
|
|
|
//! - Actor spawn rate
|
|
|
|
|
//! - Fan-out and fan-in patterns
|
|
|
|
|
//! - Ping-pong latency
|
|
|
|
|
|
|
|
|
|
use crate::harness::{black_box, Bench, BenchSuite};
|
|
|
|
|
use swactor::{
|
2026-02-05 15:54:03 +00:00
|
|
|
Ctx,
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
actor::{ActorAddress, ActorInterface},
|
|
|
|
|
runtime::{Runtime, RuntimeConfig},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Test Actors
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
/// A sink actor that counts messages received
|
|
|
|
|
struct SinkActor {
|
|
|
|
|
count: usize,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SinkActor {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self { count: 0 }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
struct Ping;
|
|
|
|
|
|
|
|
|
|
impl ActorInterface for SinkActor {
|
|
|
|
|
type Incoming = Ping;
|
|
|
|
|
type Response = ();
|
|
|
|
|
|
2026-02-05 15:54:03 +00:00
|
|
|
fn handle(&mut self, _ctx: &Ctx, _msg: Ping) {
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
self.count += 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A forwarding actor that passes messages along a chain
|
|
|
|
|
struct ForwardActor {
|
|
|
|
|
next: Option<ActorAddress>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ForwardActor {
|
|
|
|
|
fn new() -> Self {
|
|
|
|
|
Self { next: None }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn with_next(next: ActorAddress) -> Self {
|
|
|
|
|
Self { next: Some(next) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActorInterface for ForwardActor {
|
|
|
|
|
type Incoming = Ping;
|
|
|
|
|
type Response = Ping;
|
|
|
|
|
|
2026-02-05 15:54:03 +00:00
|
|
|
fn handle(&mut self, ctx: &Ctx, msg: Ping) {
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
if let Some(next) = self.next {
|
2026-02-05 15:54:03 +00:00
|
|
|
let _ = ctx.send(next, msg);
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
// Benchmarks
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
2026-02-05 15:54:03 +00:00
|
|
|
/// Benchmark: Messages sent through the runtime to a single sink actor
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
pub fn bench_message_throughput(suite: &mut BenchSuite) {
|
|
|
|
|
for msg_count in [1_000u64, 10_000, 100_000] {
|
|
|
|
|
let name = format!("message_throughput_{}", msg_count);
|
|
|
|
|
|
|
|
|
|
let result = Bench::new(&name)
|
2026-02-06 06:48:51 +00:00
|
|
|
.warmup(5)
|
|
|
|
|
.iters(100)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
.elements(msg_count)
|
|
|
|
|
.run_with_setup(
|
|
|
|
|
|| {
|
|
|
|
|
// Setup: create runtime and sink actor
|
|
|
|
|
let config = RuntimeConfig {
|
|
|
|
|
max_actors: 100,
|
|
|
|
|
actor_max_messages: (msg_count as usize) * 2,
|
|
|
|
|
num_threads: 1,
|
2026-02-06 06:48:51 +00:00
|
|
|
..Default::default()
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
};
|
|
|
|
|
let runtime = Runtime::new(config);
|
|
|
|
|
let sink = runtime.spawn(SinkActor::new()).unwrap();
|
|
|
|
|
(runtime, sink, msg_count)
|
|
|
|
|
},
|
|
|
|
|
|(runtime, sink, count)| {
|
|
|
|
|
// Send all messages
|
|
|
|
|
for _ in 0..count {
|
|
|
|
|
let _ = runtime.send_to::<Ping>(sink, Ping);
|
|
|
|
|
}
|
|
|
|
|
// Process until done
|
|
|
|
|
for _ in 0..(count * 3) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
black_box(());
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
suite.add(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Benchmark: Actor spawn rate
|
|
|
|
|
pub fn bench_spawn_rate(suite: &mut BenchSuite) {
|
|
|
|
|
for actor_count in [100u64, 500, 900] {
|
|
|
|
|
let name = format!("spawn_rate_{}_actors", actor_count);
|
|
|
|
|
|
|
|
|
|
let result = Bench::new(&name)
|
2026-02-06 06:48:51 +00:00
|
|
|
.warmup(5)
|
|
|
|
|
.iters(100)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
.elements(actor_count)
|
|
|
|
|
.run_with_setup(
|
|
|
|
|
|| {
|
|
|
|
|
let config = RuntimeConfig {
|
|
|
|
|
max_actors: 1000,
|
|
|
|
|
actor_max_messages: 100,
|
|
|
|
|
num_threads: 1,
|
2026-02-06 06:48:51 +00:00
|
|
|
..Default::default()
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
};
|
|
|
|
|
Runtime::new(config)
|
|
|
|
|
},
|
|
|
|
|
|runtime| {
|
|
|
|
|
for _ in 0..actor_count {
|
|
|
|
|
let _ = runtime.spawn(SinkActor::new());
|
|
|
|
|
}
|
2026-02-05 15:54:03 +00:00
|
|
|
// Process spawns
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
for _ in 0..(actor_count * 2) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
black_box(());
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
suite.add(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Benchmark: Fan-out (1 sender to N receivers)
|
|
|
|
|
pub fn bench_fanout(suite: &mut BenchSuite) {
|
2026-02-06 06:48:51 +00:00
|
|
|
for (fan_count, messages_per_receiver, warmup_n, iters_n) in [
|
|
|
|
|
(10u64, 500u64, 5usize, 50usize),
|
|
|
|
|
(100, 200, 5, 40),
|
|
|
|
|
(500, 100, 5, 30),
|
|
|
|
|
] {
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
let name = format!("fanout_1_to_{}", fan_count);
|
|
|
|
|
|
|
|
|
|
let result = Bench::new(&name)
|
2026-02-06 06:48:51 +00:00
|
|
|
.warmup(warmup_n)
|
|
|
|
|
.iters(iters_n)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
.elements(fan_count * messages_per_receiver)
|
|
|
|
|
.run_with_setup(
|
|
|
|
|
|| {
|
|
|
|
|
let config = RuntimeConfig {
|
|
|
|
|
max_actors: (fan_count as usize) + 10,
|
2026-02-05 15:54:03 +00:00
|
|
|
actor_max_messages: (fan_count as usize)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
* (messages_per_receiver as usize)
|
|
|
|
|
* 2,
|
|
|
|
|
num_threads: 1,
|
2026-02-06 06:48:51 +00:00
|
|
|
..Default::default()
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
};
|
|
|
|
|
let runtime = Runtime::new(config);
|
|
|
|
|
|
|
|
|
|
// Spawn N sink actors
|
|
|
|
|
let mut sinks = Vec::with_capacity(fan_count as usize);
|
|
|
|
|
for _ in 0..fan_count {
|
|
|
|
|
let addr = runtime.spawn(SinkActor::new()).unwrap();
|
|
|
|
|
sinks.push(addr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:54:03 +00:00
|
|
|
// Process spawns
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
for _ in 0..(fan_count * 2) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(runtime, sinks, messages_per_receiver)
|
|
|
|
|
},
|
|
|
|
|
|(runtime, sinks, msgs_per)| {
|
|
|
|
|
// Send messages to all sinks
|
|
|
|
|
for _ in 0..msgs_per {
|
|
|
|
|
for sink in &sinks {
|
|
|
|
|
let _ = runtime.send_to::<Ping>(*sink, Ping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process all messages
|
|
|
|
|
let total_msgs = sinks.len() as u64 * msgs_per;
|
|
|
|
|
for _ in 0..(total_msgs * 3) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
black_box(());
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
suite.add(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Benchmark: Fan-in (N senders to 1 receiver)
|
|
|
|
|
pub fn bench_fanin(suite: &mut BenchSuite) {
|
2026-02-06 06:48:51 +00:00
|
|
|
for (sender_count, messages_per_sender, warmup_n, iters_n) in [
|
|
|
|
|
(10u64, 300u64, 5usize, 50usize),
|
|
|
|
|
(100, 100, 5, 30),
|
|
|
|
|
(500, 100, 5, 20),
|
|
|
|
|
] {
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
let name = format!("fanin_{}_to_1", sender_count);
|
|
|
|
|
|
|
|
|
|
let result = Bench::new(&name)
|
2026-02-06 06:48:51 +00:00
|
|
|
.warmup(warmup_n)
|
|
|
|
|
.iters(iters_n)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
.elements(sender_count * messages_per_sender)
|
|
|
|
|
.run_with_setup(
|
|
|
|
|
|| {
|
|
|
|
|
let total_messages = (sender_count * messages_per_sender) as usize;
|
|
|
|
|
let config = RuntimeConfig {
|
|
|
|
|
max_actors: (sender_count as usize) + 10,
|
2026-02-05 15:54:03 +00:00
|
|
|
actor_max_messages: total_messages * 3,
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
num_threads: 1,
|
2026-02-06 06:48:51 +00:00
|
|
|
..Default::default()
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
};
|
|
|
|
|
let runtime = Runtime::new(config);
|
|
|
|
|
|
|
|
|
|
// Spawn the sink
|
|
|
|
|
let sink = runtime.spawn(SinkActor::new()).unwrap();
|
|
|
|
|
|
|
|
|
|
// Spawn N forwarders pointing at sink
|
|
|
|
|
let mut senders = Vec::with_capacity(sender_count as usize);
|
|
|
|
|
for _ in 0..sender_count {
|
|
|
|
|
let addr = runtime.spawn(ForwardActor::with_next(sink)).unwrap();
|
|
|
|
|
senders.push(addr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:54:03 +00:00
|
|
|
// Process spawns
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
for _ in 0..((sender_count + 1) * 2) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(runtime, senders, sink, messages_per_sender)
|
|
|
|
|
},
|
|
|
|
|
|(runtime, senders, _sink, msgs_per)| {
|
|
|
|
|
// Each sender forwards msgs_per messages to the sink
|
|
|
|
|
for _ in 0..msgs_per {
|
|
|
|
|
for sender in &senders {
|
|
|
|
|
let _ = runtime.send_to::<Ping>(*sender, Ping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process all messages (forwarder receives + forwards, sink receives)
|
|
|
|
|
let total_msgs = senders.len() as u64 * msgs_per;
|
|
|
|
|
for _ in 0..(total_msgs * 6) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
black_box(());
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
suite.add(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Benchmark: Ring topology (message passed around N actors in a circle)
|
|
|
|
|
pub fn bench_ring(suite: &mut BenchSuite) {
|
2026-02-06 06:48:51 +00:00
|
|
|
for (ring_size, laps, warmup_n, iters_n) in [
|
|
|
|
|
(10u64, 100u64, 5usize, 100usize),
|
|
|
|
|
(100, 20, 5, 50),
|
|
|
|
|
(500, 10, 5, 40),
|
|
|
|
|
] {
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
let name = format!("ring_{}_actors", ring_size);
|
|
|
|
|
|
|
|
|
|
let result = Bench::new(&name)
|
2026-02-06 06:48:51 +00:00
|
|
|
.warmup(warmup_n)
|
|
|
|
|
.iters(iters_n)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
.elements(ring_size * laps)
|
|
|
|
|
.run_with_setup(
|
|
|
|
|
|| {
|
|
|
|
|
let config = RuntimeConfig {
|
|
|
|
|
max_actors: (ring_size as usize) + 10,
|
2026-02-05 15:54:03 +00:00
|
|
|
actor_max_messages: 10_000,
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
num_threads: 1,
|
2026-02-06 06:48:51 +00:00
|
|
|
..Default::default()
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
};
|
|
|
|
|
let runtime = Runtime::new(config);
|
|
|
|
|
|
|
|
|
|
// First, spawn all actors without links
|
|
|
|
|
let mut actors: Vec<ActorAddress> = Vec::with_capacity(ring_size as usize);
|
|
|
|
|
for _ in 0..ring_size {
|
|
|
|
|
let addr = runtime.spawn(ForwardActor::new()).unwrap();
|
|
|
|
|
actors.push(addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We can't update their `next` field after spawn in this design,
|
|
|
|
|
// so instead we'll use an inbox to receive the final message
|
|
|
|
|
// For now, we'll just measure message passing through a chain
|
|
|
|
|
|
2026-02-05 15:54:03 +00:00
|
|
|
// Process spawns
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
for _ in 0..(ring_size * 2) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
(runtime, actors, laps)
|
|
|
|
|
},
|
|
|
|
|
|(runtime, actors, laps)| {
|
|
|
|
|
// Send to first actor (even though they don't forward, we're
|
2026-02-05 15:54:03 +00:00
|
|
|
// measuring the transfer queue + inbox overhead)
|
feat: Stress tests, benchmarking, and non-failing queues and inboxes #3
Adds some basic benchmarking, stress tests. They still need to be properly examined to ensure they are testing the correct properties, but fit for "good enough". Implements the HybridChannel type, which features a channel buffer that can withstand overflows. It does so by providing a dequeue behind a mutex. Without overflow, will push messages into the lock free ArrayQueue implemented by crossbeam_queue; when that buffer fills, will use the locking portion provided by the Mutex<VecDequeue>.
In the future we can even further optimize this, perhaps with some linked list implementations of lock-free channels, but, like the benchmarks, this fits the "good enough" bar for now.
2026-01-26 07:14:00 +00:00
|
|
|
for _ in 0..laps {
|
|
|
|
|
for actor in &actors {
|
|
|
|
|
let _ = runtime.send_to::<Ping>(*actor, Ping);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let total = actors.len() as u64 * laps;
|
|
|
|
|
for _ in 0..(total * 3) {
|
|
|
|
|
runtime.tick();
|
|
|
|
|
}
|
|
|
|
|
black_box(());
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
suite.add(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Run all throughput benchmarks
|
|
|
|
|
pub fn run_all() -> BenchSuite {
|
|
|
|
|
let mut suite = BenchSuite::new("Throughput Benchmarks");
|
|
|
|
|
|
|
|
|
|
println!("\nRunning message throughput benchmarks...");
|
|
|
|
|
bench_message_throughput(&mut suite);
|
|
|
|
|
|
|
|
|
|
println!("Running spawn rate benchmarks...");
|
|
|
|
|
bench_spawn_rate(&mut suite);
|
|
|
|
|
|
|
|
|
|
println!("Running fan-out benchmarks...");
|
|
|
|
|
bench_fanout(&mut suite);
|
|
|
|
|
|
|
|
|
|
println!("Running fan-in benchmarks...");
|
|
|
|
|
bench_fanin(&mut suite);
|
|
|
|
|
|
|
|
|
|
println!("Running ring topology benchmarks...");
|
|
|
|
|
bench_ring(&mut suite);
|
|
|
|
|
|
|
|
|
|
suite
|
|
|
|
|
}
|