swactor/benches/scaling.rs
Zachery Aaron Shores-Chmielewski c80060f453 fix: consolidate modules
Better consoliation of modules into more logical components.
2026-02-06 14:06:37 +07:00

283 lines
8.8 KiB
Rust

//! Scaling benchmarks for the swactor runtime.
//!
//! These benchmarks measure how performance scales with:
//! - Number of actors
//! - Number of worker threads
//! - Message payload size
use crate::harness::{black_box, Bench, BenchSuite};
use std::thread;
use swactor::{
actor::ActorInterface,
runtime::{Ctx, Runtime, RuntimeConfig},
};
// ============================================================================
// Test Actors
// ============================================================================
/// A counter actor that just increments on each message
struct CounterActor {
count: usize,
}
impl CounterActor {
fn new() -> Self {
Self { count: 0 }
}
}
#[derive(Clone)]
struct Increment;
impl ActorInterface for CounterActor {
type Incoming = Increment;
type Response = ();
fn handle(&mut self, _ctx: &Ctx, _msg: Increment) {
self.count += 1;
}
}
struct SharedCounter {
count: std::sync::Arc<std::sync::atomic::AtomicUsize>,
}
impl ActorInterface for SharedCounter {
type Incoming = Increment;
type Response = ();
fn handle(&mut self, _ctx: &Ctx, _msg: Increment) {
self.count
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
/// An actor that handles variable-sized payloads
struct PayloadActor {
bytes_received: usize,
}
impl PayloadActor {
fn new() -> Self {
Self { bytes_received: 0 }
}
}
#[derive(Clone)]
struct Payload(Vec<u8>);
impl ActorInterface for PayloadActor {
type Incoming = Payload;
type Response = ();
fn handle(&mut self, _ctx: &Ctx, msg: Payload) {
self.bytes_received += msg.0.len();
black_box(&msg.0);
}
}
// ============================================================================
// Benchmarks
// ============================================================================
/// Benchmark: How throughput scales with actor count
pub fn bench_actor_count_scaling(suite: &mut BenchSuite) {
for (actor_count, messages_per_actor, warmup_n, iters_n) in [
(10u64, 200u64, 5usize, 50usize),
(100, 200, 5, 30),
(500, 200, 5, 15),
(1000, 200, 5, 12),
] {
let name = format!("scaling_{}_actors", actor_count);
let total_messages = actor_count * messages_per_actor;
let result = Bench::new(&name)
.warmup(warmup_n)
.iters(iters_n)
.elements(total_messages)
.run_with_setup(
|| {
let config = RuntimeConfig {
max_actors: (actor_count as usize) + 100,
actor_max_messages: (total_messages as usize) * 3,
num_threads: 1,
..Default::default()
};
let runtime = Runtime::new(config);
// Spawn actors
let mut actors = Vec::with_capacity(actor_count as usize);
for _ in 0..actor_count {
let addr = runtime.spawn(CounterActor::new()).unwrap();
actors.push(addr);
}
// Process spawns
for _ in 0..(actor_count * 2) {
runtime.tick();
}
(runtime, actors, messages_per_actor)
},
|(runtime, actors, msgs_per)| {
// Distribute messages across all actors
for _ in 0..msgs_per {
for actor in &actors {
let _ = runtime.send_to::<Increment>(*actor, Increment);
}
}
// Process all
let total = actors.len() as u64 * msgs_per;
for _ in 0..(total * 3) {
runtime.tick();
}
black_box(());
},
);
suite.add(result);
}
}
/// Benchmark: How throughput scales with thread count (multithreaded runtime)
pub fn bench_thread_count_scaling(suite: &mut BenchSuite) {
let actor_count = 100u64;
let messages_per_actor = 500u64;
let total_messages = actor_count * messages_per_actor;
for thread_count in [2usize, 4, 8] {
let name = format!("scaling_{}_threads", thread_count);
let result = Bench::new(&name)
.warmup(5)
.iters(30)
.elements(total_messages)
.run_with_setup(
|| {
let counter = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
let config = RuntimeConfig {
max_actors: (actor_count as usize) + 100,
actor_max_messages: (total_messages as usize) * 3,
num_threads: thread_count,
..Default::default()
};
let runtime = Runtime::new(config);
let mut actors = Vec::with_capacity(actor_count as usize);
for _ in 0..actor_count {
let addr = runtime
.spawn(SharedCounter {
count: counter.clone(),
})
.unwrap();
actors.push(addr);
}
let handle = runtime.run().unwrap();
for actor in &actors {
loop {
if handle
.runtime
.send_to::<Increment>(*actor, Increment)
.is_ok()
{
break;
}
thread::yield_now();
}
}
while counter.load(std::sync::atomic::Ordering::Relaxed) < actors.len() {
thread::yield_now();
}
counter.store(0, std::sync::atomic::Ordering::Relaxed);
(handle, actors, counter)
},
|(handle, actors, counter)| {
for _ in 0..messages_per_actor {
for actor in &actors {
let _ = handle.runtime.send_to::<Increment>(*actor, Increment);
}
}
while counter.load(std::sync::atomic::Ordering::Relaxed)
< total_messages as usize
{
thread::yield_now();
}
handle.shutdown();
handle.join();
black_box(());
},
);
suite.add(result);
}
}
/// Benchmark: How throughput scales with message payload size
pub fn bench_payload_size_scaling(suite: &mut BenchSuite) {
let message_count = 3_000u64;
for payload_size in [64usize, 1024, 16384, 65536] {
let name = format!("payload_{}B", payload_size);
let payload = vec![0u8; payload_size];
let result = Bench::new(&name)
.warmup(5)
.iters(40)
.elements(message_count)
.run_with_setup(
|| {
let config = RuntimeConfig {
max_actors: 10,
actor_max_messages: (message_count as usize) * 2,
num_threads: 1,
..Default::default()
};
let runtime = Runtime::new(config);
let sink = runtime.spawn(PayloadActor::new()).unwrap();
// Process spawn
for _ in 0..10 {
runtime.tick();
}
(runtime, sink, payload.clone())
},
|(runtime, sink, payload)| {
for _ in 0..message_count {
let _ = runtime.send_to::<Payload>(sink, Payload(payload.clone()));
}
for _ in 0..(message_count * 3) {
runtime.tick();
}
black_box(());
},
);
suite.add(result);
}
}
/// Run all scaling benchmarks
pub fn run_all() -> BenchSuite {
let mut suite = BenchSuite::new("Scaling Benchmarks");
println!("\nRunning actor count scaling benchmarks...");
bench_actor_count_scaling(&mut suite);
println!("Running thread count scaling benchmarks...");
bench_thread_count_scaling(&mut suite);
println!("Running payload size scaling benchmarks...");
bench_payload_size_scaling(&mut suite);
suite
}