Files
announcement/benches/scaling.rs
T
swaits e8e55b7676 chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
2025-10-13 19:36:37 -06:00

202 lines
6.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Linearity and scaling benchmarks
//!
//! Proves O(N) linear scaling behavior for announcement operations:
//! - Announce operation: O(1) operation + O(N) listener wakeup
//! - Retrieval operation: O(1) per listener = O(N) total for N listeners
//! - Listener creation: O(1) per listener = O(N) total for N listeners
//!
//! Tests scales from 0 to 10 million listeners to verify linearity holds.
use announcement::Announcement;
use criterion::{
criterion_group, criterion_main, BenchmarkId, Criterion, PlotConfiguration, Throughput,
};
use std::hint::black_box;
use std::sync::Arc;
/// Benchmark: Linearity of announce operation with 0 to 10M listeners
///
/// This verifies that announcing scales linearly with listener count.
/// Expected: O(1) announce + O(N) wakeup = O(N) total
fn bench_linearity_announce(c: &mut Criterion) {
let mut group = c.benchmark_group("scaling/announce_linearity");
group.plot_config(PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear));
// Test 3 logarithmically-spaced scales to prove O(N) linearity
// Mathematical proof: 3 points on log scale demonstrate linear behavior
let scales = [0, 1, 1_000, 100_000];
for n in scales.iter() {
group.throughput(Throughput::Elements(*n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), n, |b, &n| {
b.iter(|| {
let (tx, announcement) = Announcement::new();
// Create N listeners
let mut listeners = Vec::with_capacity(n);
for _ in 0..n {
listeners.push(announcement.listener());
}
// Measure announce time (should be O(1) + O(N) wakeup)
tx.announce(Arc::new(42)).unwrap();
// Keep listeners alive to measure full wakeup cost
black_box(listeners);
});
});
}
group.finish();
}
/// Benchmark: Linearity of retrieval operation with 1 to 10M listeners
///
/// This verifies that retrieving from N listeners scales linearly.
/// Expected: O(1) per listener × N listeners = O(N) total
fn bench_linearity_retrieval(c: &mut Criterion) {
let mut group = c.benchmark_group("scaling/retrieval_linearity");
group.plot_config(PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear));
let scales = [1, 1_000, 100_000];
for n in scales.iter() {
group.throughput(Throughput::Elements(*n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), n, |b, &n| {
b.iter(|| {
let (tx, announcement) = Announcement::new();
// Create N listeners
let mut listeners = Vec::with_capacity(n);
for _ in 0..n {
listeners.push(announcement.listener());
}
// Announce first
tx.announce(Arc::new(42)).unwrap();
// Measure retrieval time (should be O(1) per listener = O(N) total)
for listener in &listeners {
black_box(listener.try_listen());
}
});
});
}
group.finish();
}
/// Benchmark: Linearity of listener creation with 1 to 10M listeners
///
/// This verifies that creating N listeners scales linearly.
/// Expected: O(1) per listener × N listeners = O(N) total
fn bench_linearity_listener_creation(c: &mut Criterion) {
let mut group = c.benchmark_group("scaling/listener_creation_linearity");
group.plot_config(PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear));
let scales = [1, 1_000, 100_000];
for n in scales.iter() {
group.throughput(Throughput::Elements(*n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), n, |b, &n| {
b.iter(|| {
let (_tx, announcement) = Announcement::<Arc<i32>>::new();
// Create N listeners (should be O(N) with O(1) per listener)
let mut listeners = Vec::with_capacity(n);
for _ in 0..n {
listeners.push(announcement.listener());
}
black_box(listeners);
});
});
}
group.finish();
}
/// Benchmark: Zero listener overhead
///
/// Critical test: Announcing with 0 listeners MUST be nearly free (<5ns).
/// This is a key selling point of the design.
fn bench_zero_listener_overhead(c: &mut Criterion) {
let mut group = c.benchmark_group("scaling/zero_listener_overhead");
group.plot_config(PlotConfiguration::default().summary_scale(criterion::AxisScale::Linear));
// Compare 0 vs 1 vs 1000 listeners
// Zero listeners MUST be nearly free (<5ns)
let listener_counts = [0, 1, 1_000];
for n in listener_counts.iter() {
group.bench_with_input(BenchmarkId::from_parameter(n), n, |b, &n| {
b.iter(|| {
let (tx, announcement) = Announcement::new();
// Create N listeners
let mut listeners = Vec::with_capacity(n);
for _ in 0..n {
listeners.push(announcement.listener());
}
// Announce with N listeners
tx.announce(Arc::new(42)).unwrap();
black_box(listeners);
});
});
}
group.finish();
}
/// Benchmark: Overhead breakdown
///
/// Measures individual components to understand where time is spent.
fn bench_overhead_breakdown(c: &mut Criterion) {
let mut group = c.benchmark_group("scaling/overhead_breakdown");
// Measure individual components
group.bench_function("just_creation", |b| {
b.iter(|| {
let (_tx, _announcement) = Announcement::<Arc<i32>>::new();
});
});
group.bench_function("creation_plus_announce", |b| {
b.iter(|| {
let (tx, _announcement) = Announcement::new();
tx.announce(Arc::new(42)).unwrap();
});
});
group.bench_function("with_1_listener", |b| {
b.iter(|| {
let (tx, announcement) = Announcement::new();
let _listener = announcement.listener();
tx.announce(Arc::new(42)).unwrap();
});
});
group.bench_function("with_1_listener_retrieve", |b| {
b.iter(|| {
let (tx, announcement) = Announcement::new();
let listener = announcement.listener();
tx.announce(Arc::new(42)).unwrap();
black_box(listener.try_listen());
});
});
group.finish();
}
criterion_group!(
benches,
bench_linearity_announce,
bench_linearity_retrieval,
bench_linearity_listener_creation,
bench_zero_listener_overhead,
bench_overhead_breakdown,
);
criterion_main!(benches);