chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
64 files changed
+23966
No files matched your search
+128
@@ -0,0 +1,128 @@
|
||||
//! Core operation benchmarks
|
||||
//!
|
||||
//! Measures the fundamental performance of basic announcement operations:
|
||||
//! - Channel creation overhead
|
||||
//! - Listener creation cost
|
||||
//! - Announcement without listeners
|
||||
//! - Try-listen operations (hit and miss cases)
|
||||
//! - Status check operations
|
||||
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use std::hint::black_box;
|
||||
|
||||
/// Benchmark: Create a new announcement channel
|
||||
fn bench_channel_creation(c: &mut Criterion) {
|
||||
c.bench_function("core/channel_creation", |b| {
|
||||
b.iter(|| {
|
||||
let (_tx, _announcement) = Announcement::<i32>::new();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmark: Create a listener from an existing announcement
|
||||
fn bench_listener_creation(c: &mut Criterion) {
|
||||
let (_tx, announcement) = Announcement::<i32>::new();
|
||||
|
||||
c.bench_function("core/listener_creation", |b| {
|
||||
b.iter(|| black_box(announcement.listener()));
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmark: Announce with no listeners attached
|
||||
fn bench_announce_no_listeners(c: &mut Criterion) {
|
||||
c.bench_function("core/announce_no_listeners", |b| {
|
||||
b.iter(|| {
|
||||
let (tx, _) = Announcement::<i32>::new();
|
||||
tx.announce(black_box(42)).unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmark: try_listen when value is available (cache hit)
|
||||
fn bench_try_listen_hit(c: &mut Criterion) {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
tx.announce(42).unwrap();
|
||||
let listener = announcement.listener();
|
||||
|
||||
c.bench_function("core/try_listen_hit", |b| {
|
||||
b.iter(|| {
|
||||
black_box(listener.try_listen());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmark: try_listen when no value available (cache miss)
|
||||
fn bench_try_listen_miss(c: &mut Criterion) {
|
||||
let (_tx, announcement) = Announcement::<i32>::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
c.bench_function("core/try_listen_miss", |b| {
|
||||
b.iter(|| {
|
||||
black_box(listener.try_listen());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmark: Check is_announced status
|
||||
fn bench_is_announced(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("core/is_announced");
|
||||
|
||||
// Not yet announced
|
||||
let (_tx_not, announcement_not) = Announcement::<i32>::new();
|
||||
group.bench_function("before_announce", |b| {
|
||||
b.iter(|| {
|
||||
black_box(announcement_not.is_announced());
|
||||
});
|
||||
});
|
||||
|
||||
// Already announced
|
||||
let (tx, announcement_yes) = Announcement::<i32>::new();
|
||||
let _result = tx.announce(42);
|
||||
group.bench_function("after_announce", |b| {
|
||||
b.iter(|| {
|
||||
black_box(announcement_yes.is_announced());
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Check is_closed status
|
||||
fn bench_is_closed(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("core/is_closed");
|
||||
|
||||
// Not yet closed
|
||||
let (_tx, announcement) = Announcement::<i32>::new();
|
||||
group.bench_function("announcer_alive", |b| {
|
||||
b.iter(|| {
|
||||
black_box(announcement.is_closed());
|
||||
});
|
||||
});
|
||||
|
||||
// After dropping announcer
|
||||
let announcement_closed = {
|
||||
let (tx, announcement) = Announcement::<i32>::new();
|
||||
drop(tx);
|
||||
announcement
|
||||
};
|
||||
group.bench_function("announcer_dropped", |b| {
|
||||
b.iter(|| {
|
||||
black_box(announcement_closed.is_closed());
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_channel_creation,
|
||||
bench_listener_creation,
|
||||
bench_announce_no_listeners,
|
||||
bench_try_listen_hit,
|
||||
bench_try_listen_miss,
|
||||
bench_is_announced,
|
||||
bench_is_closed,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,564 @@
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::hint::black_box;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
// ============================================================================
|
||||
// SHUTDOWN SIGNAL PATTERN - Notify all workers to shut down
|
||||
// ============================================================================
|
||||
|
||||
fn bench_shutdown_signal(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/shutdown_signal");
|
||||
|
||||
// Simulate shutdown signal to various numbers of workers
|
||||
for num_workers in [10, 100, 1_000, 10_000].iter() {
|
||||
group.throughput(Throughput::Elements(*num_workers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_workers),
|
||||
num_workers,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (shutdown_tx, shutdown_announcement) = Announcement::new();
|
||||
|
||||
// Create N worker listeners
|
||||
let mut workers = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
workers.push(shutdown_announcement.listener());
|
||||
}
|
||||
|
||||
// Send shutdown signal
|
||||
shutdown_tx.announce(Arc::new(())).unwrap();
|
||||
|
||||
// All workers check for shutdown
|
||||
for worker in &workers {
|
||||
black_box(worker.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_shutdown_with_tokio_tasks(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/shutdown_async");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
for num_workers in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_workers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_workers),
|
||||
num_workers,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (shutdown_tx, shutdown_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N worker tasks
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = shutdown_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
// Wait for shutdown
|
||||
listener.listen().await.unwrap();
|
||||
i
|
||||
}));
|
||||
}
|
||||
|
||||
// Small delay to let tasks start
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// Send shutdown signal
|
||||
shutdown_tx.announce(Arc::new(())).unwrap();
|
||||
|
||||
// Wait for all workers to complete
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONFIG BROADCAST PATTERN - Share configuration to all services
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Config {
|
||||
database_url: String,
|
||||
api_key: String,
|
||||
max_connections: usize,
|
||||
timeout_ms: u64,
|
||||
}
|
||||
|
||||
fn bench_config_broadcast(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/config_broadcast");
|
||||
|
||||
for num_services in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_services as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_services),
|
||||
num_services,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (config_tx, config_announcement) = Announcement::new();
|
||||
|
||||
// Create N service listeners
|
||||
let mut services = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
services.push(config_announcement.listener());
|
||||
}
|
||||
|
||||
// Broadcast configuration
|
||||
let config = Arc::new(Config {
|
||||
database_url: "postgres://localhost/db".to_string(),
|
||||
api_key: "secret-key-12345".to_string(),
|
||||
max_connections: 100,
|
||||
timeout_ms: 5000,
|
||||
});
|
||||
|
||||
config_tx.announce(config).unwrap();
|
||||
|
||||
// All services receive config
|
||||
for service in &services {
|
||||
black_box(service.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_config_broadcast_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/config_broadcast_async");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
for num_services in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_services as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_services),
|
||||
num_services,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (config_tx, config_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N service tasks waiting for config
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = config_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
// Wait for config
|
||||
let config: Arc<Config> = listener.listen().await.unwrap();
|
||||
// Use config
|
||||
(i, config.max_connections)
|
||||
}));
|
||||
}
|
||||
|
||||
// Small delay to let tasks start
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// Broadcast configuration
|
||||
let config = Arc::new(Config {
|
||||
database_url: "postgres://localhost/db".to_string(),
|
||||
api_key: "secret-key-12345".to_string(),
|
||||
max_connections: 100,
|
||||
timeout_ms: 5000,
|
||||
});
|
||||
|
||||
config_tx.announce(config).unwrap();
|
||||
|
||||
// Wait for all services to process config
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EVENT FANOUT PATTERN - Broadcast events to many subscribers
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Event {
|
||||
event_type: String,
|
||||
timestamp: u64,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
fn bench_event_fanout(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/event_fanout");
|
||||
|
||||
for num_subscribers in [10, 100, 1_000].iter() {
|
||||
group.throughput(Throughput::Elements(*num_subscribers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_subscribers),
|
||||
num_subscribers,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (event_tx, event_announcement) = Announcement::new();
|
||||
|
||||
// Create N subscriber listeners
|
||||
let mut subscribers = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
subscribers.push(event_announcement.listener());
|
||||
}
|
||||
|
||||
// Broadcast event
|
||||
let event = Arc::new(Event {
|
||||
event_type: "user.created".to_string(),
|
||||
timestamp: 1234567890,
|
||||
data: vec![1, 2, 3, 4, 5],
|
||||
});
|
||||
|
||||
event_tx.announce(event).unwrap();
|
||||
|
||||
// All subscribers receive event
|
||||
for subscriber in &subscribers {
|
||||
black_box(subscriber.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_event_fanout_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/event_fanout_async");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
for num_subscribers in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_subscribers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_subscribers),
|
||||
num_subscribers,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (event_tx, event_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N subscriber tasks
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = event_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
let event: Arc<Event> = listener.listen().await.unwrap();
|
||||
(i, event.timestamp)
|
||||
}));
|
||||
}
|
||||
|
||||
// Small delay
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// Broadcast event
|
||||
let event = Arc::new(Event {
|
||||
event_type: "user.created".to_string(),
|
||||
timestamp: 1234567890,
|
||||
data: vec![1, 2, 3, 4, 5],
|
||||
});
|
||||
|
||||
event_tx.announce(event).unwrap();
|
||||
|
||||
// Wait for all subscribers
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// REQUEST-RESPONSE MULTIPLEXING - Multiple waiters for single response
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Response {
|
||||
status: u16,
|
||||
body: String,
|
||||
}
|
||||
|
||||
fn bench_request_response_multiplex(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/request_response_multiplex");
|
||||
|
||||
// Multiple tasks waiting for same HTTP response
|
||||
for num_waiters in [2, 5, 10, 50, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_waiters as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_waiters),
|
||||
num_waiters,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (response_tx, response_announcement) = Announcement::new();
|
||||
|
||||
// Create N waiters for the same response
|
||||
let mut waiters = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
waiters.push(response_announcement.listener());
|
||||
}
|
||||
|
||||
// Simulate response arrives
|
||||
let response = Arc::new(Response {
|
||||
status: 200,
|
||||
body: "OK".to_string(),
|
||||
});
|
||||
|
||||
response_tx.announce(response).unwrap();
|
||||
|
||||
// All waiters get response
|
||||
for waiter in &waiters {
|
||||
black_box(waiter.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_request_response_multiplex_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/request_response_multiplex_async");
|
||||
group.sample_size(10);
|
||||
|
||||
for num_waiters in [2, 5, 10, 50, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_waiters as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_waiters),
|
||||
num_waiters,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (response_tx, response_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N tasks waiting for response
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = response_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
let response: Arc<Response> = listener.listen().await.unwrap();
|
||||
(i, response.status)
|
||||
}));
|
||||
}
|
||||
|
||||
// Response arrives
|
||||
let response = Arc::new(Response {
|
||||
status: 200,
|
||||
body: "OK".to_string(),
|
||||
});
|
||||
|
||||
response_tx.announce(response).unwrap();
|
||||
|
||||
// Wait for all waiters
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LAZY INITIALIZATION PATTERN - Signal when resource is ready
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Database {
|
||||
connection_pool: Arc<Vec<String>>,
|
||||
max_connections: usize,
|
||||
}
|
||||
|
||||
fn bench_lazy_initialization(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/lazy_initialization");
|
||||
|
||||
for num_clients in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_clients as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_clients),
|
||||
num_clients,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (db_ready_tx, db_ready_announcement) = Announcement::new();
|
||||
|
||||
// N clients waiting for database to be ready
|
||||
let mut clients = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
clients.push(db_ready_announcement.listener());
|
||||
}
|
||||
|
||||
// Simulate database initialization
|
||||
let db = Arc::new(Database {
|
||||
connection_pool: Arc::new(vec![
|
||||
"conn1".to_string(),
|
||||
"conn2".to_string(),
|
||||
"conn3".to_string(),
|
||||
]),
|
||||
max_connections: 10,
|
||||
});
|
||||
|
||||
// Signal ready
|
||||
db_ready_tx.announce(db).unwrap();
|
||||
|
||||
// All clients can now proceed
|
||||
for client in &clients {
|
||||
black_box(client.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_lazy_initialization_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/lazy_initialization_async");
|
||||
group.sample_size(10);
|
||||
|
||||
for num_clients in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_clients as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_clients),
|
||||
num_clients,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (db_ready_tx, db_ready_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N client tasks
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = db_ready_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
// Wait for database
|
||||
let db: Arc<Database> = listener.listen().await.unwrap();
|
||||
(i, db.max_connections)
|
||||
}));
|
||||
}
|
||||
|
||||
let db = Arc::new(Database {
|
||||
connection_pool: Arc::new(vec![
|
||||
"conn1".to_string(),
|
||||
"conn2".to_string(),
|
||||
"conn3".to_string(),
|
||||
]),
|
||||
max_connections: 10,
|
||||
});
|
||||
|
||||
db_ready_tx.announce(db).unwrap();
|
||||
|
||||
// Wait for all clients
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BARRIER PATTERN - Multiple announcements that all must complete
|
||||
// ============================================================================
|
||||
|
||||
fn bench_barrier_pattern(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/barrier");
|
||||
|
||||
// N tasks each announce completion, all wait for all to complete
|
||||
for num_tasks in [5, 10, 25].iter() {
|
||||
group.throughput(Throughput::Elements(*num_tasks as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_tasks),
|
||||
num_tasks,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let mut announcements = Vec::with_capacity(n);
|
||||
let mut announcers = Vec::with_capacity(n);
|
||||
|
||||
// Create N announcement channels (one per task)
|
||||
for _ in 0..n {
|
||||
let (tx, announcement) = Announcement::<Arc<i32>>::new();
|
||||
announcers.push(tx);
|
||||
announcements.push(announcement);
|
||||
}
|
||||
|
||||
// Each task listens to all other tasks
|
||||
let mut all_listeners = Vec::with_capacity(n * n);
|
||||
for announcement in &announcements {
|
||||
for _ in 0..n {
|
||||
all_listeners.push(announcement.listener());
|
||||
}
|
||||
}
|
||||
|
||||
// All tasks complete
|
||||
for (i, announcer) in announcers.into_iter().enumerate() {
|
||||
announcer.announce(Arc::new(i as i32)).unwrap();
|
||||
}
|
||||
|
||||
// Verify all received
|
||||
for listener in &all_listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
shutdown,
|
||||
bench_shutdown_signal,
|
||||
bench_shutdown_with_tokio_tasks,
|
||||
);
|
||||
|
||||
criterion_group!(config, bench_config_broadcast, bench_config_broadcast_async,);
|
||||
|
||||
criterion_group!(events, bench_event_fanout, bench_event_fanout_async,);
|
||||
|
||||
criterion_group!(
|
||||
multiplex,
|
||||
bench_request_response_multiplex,
|
||||
bench_request_response_multiplex_async,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
lazy_init,
|
||||
bench_lazy_initialization,
|
||||
bench_lazy_initialization_async,
|
||||
);
|
||||
|
||||
criterion_group!(barrier, bench_barrier_pattern,);
|
||||
|
||||
criterion_main!(shutdown, config, events, multiplex, lazy_init, barrier);
|
||||
@@ -0,0 +1,165 @@
|
||||
//! Runtime comparison benchmarks
|
||||
//!
|
||||
//! Compares announcement performance across different async runtimes.
|
||||
//! This verifies the runtime-agnostic design works consistently.
|
||||
//!
|
||||
//! Runtimes tested:
|
||||
//! - tokio (most popular)
|
||||
//! - smol (lightweight alternative)
|
||||
//!
|
||||
//! Expected result: Similar performance across runtimes since announcement
|
||||
//! uses event-listener which abstracts runtime details.
|
||||
|
||||
#![allow(unused_must_use)]
|
||||
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::hint::black_box;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Benchmark: Basic announce and listen - tokio vs smol
|
||||
fn bench_basic(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("runtimes/basic");
|
||||
|
||||
// Tokio version
|
||||
group.bench_function("tokio", |b| {
|
||||
b.to_async(&rt).iter(|| async {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let handle = tokio::spawn(async move { listener.listen().await });
|
||||
announcer.announce(Arc::new(42)).unwrap();
|
||||
|
||||
black_box(handle.await.unwrap());
|
||||
});
|
||||
});
|
||||
|
||||
// Smol version
|
||||
group.bench_function("smol", |b| {
|
||||
b.iter(|| {
|
||||
smol::block_on(async {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let handle = smol::spawn(async move { listener.listen().await });
|
||||
announcer.announce(Arc::new(42)).unwrap();
|
||||
|
||||
black_box(handle.await);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Fanout to multiple listeners - tokio vs smol
|
||||
fn bench_fanout(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("runtimes/fanout");
|
||||
|
||||
for n in [10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// Tokio version
|
||||
group.bench_with_input(BenchmarkId::new("tokio", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let listener = announcement.listener();
|
||||
handles.push(tokio::spawn(async move { listener.listen().await }));
|
||||
}
|
||||
|
||||
announcer.announce(Arc::new(42)).unwrap();
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Smol version
|
||||
group.bench_with_input(BenchmarkId::new("smol", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
smol::block_on(async {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let listener = announcement.listener();
|
||||
handles.push(smol::spawn(async move { listener.listen().await }));
|
||||
}
|
||||
|
||||
announcer.announce(Arc::new(42)).unwrap();
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Concurrent announcements - tokio vs smol
|
||||
fn bench_concurrent(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("runtimes/concurrent");
|
||||
|
||||
// Tokio version
|
||||
group.bench_function("tokio", |b| {
|
||||
b.to_async(&rt).iter(|| async {
|
||||
let mut handles = Vec::with_capacity(100);
|
||||
|
||||
for i in 0..100 {
|
||||
handles.push(tokio::spawn(async move {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let recv_handle = tokio::spawn(async move { listener.listen().await });
|
||||
announcer.announce(Arc::new(i)).unwrap();
|
||||
|
||||
recv_handle.await.unwrap()
|
||||
}));
|
||||
}
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Smol version
|
||||
group.bench_function("smol", |b| {
|
||||
b.iter(|| {
|
||||
smol::block_on(async {
|
||||
let mut handles = Vec::with_capacity(100);
|
||||
|
||||
for i in 0..100 {
|
||||
handles.push(smol::spawn(async move {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let recv_handle = smol::spawn(async move { listener.listen().await });
|
||||
announcer.announce(Arc::new(i)).unwrap();
|
||||
|
||||
recv_handle.await
|
||||
}));
|
||||
}
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_basic, bench_fanout, bench_concurrent,);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,201 @@
|
||||
//! 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);
|
||||
@@ -0,0 +1,231 @@
|
||||
//! Stress test benchmarks for extreme scenarios
|
||||
//!
|
||||
//! Tests announcement under extreme conditions:
|
||||
//! - Million-scale operations (1M+ listeners/announcements)
|
||||
//! - Fully connected networks (N announcements × N listeners)
|
||||
//! - Cascade patterns (chain reactions and trees)
|
||||
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::hint::black_box;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Benchmark: Million scale stress - 1M announcements × 10 listeners
|
||||
fn bench_million_announcements(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("stress/million_scale");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(30));
|
||||
|
||||
group.bench_function("10K_announcements_x_10_listeners", |b| {
|
||||
b.iter(|| {
|
||||
let num_announcements = 10_000;
|
||||
let listeners_per_announcement = 10;
|
||||
|
||||
let mut announcements = Vec::with_capacity(num_announcements);
|
||||
let mut announcers = Vec::with_capacity(num_announcements);
|
||||
|
||||
// Create 10K announcement channels
|
||||
for _ in 0..num_announcements {
|
||||
let (tx, announcement) = Announcement::<Arc<i32>>::new();
|
||||
announcers.push(tx);
|
||||
announcements.push(announcement);
|
||||
}
|
||||
|
||||
// Create 10 listeners for each announcement (100K total)
|
||||
let mut all_listeners =
|
||||
Vec::with_capacity(num_announcements * listeners_per_announcement);
|
||||
for announcement in &announcements {
|
||||
for _ in 0..listeners_per_announcement {
|
||||
all_listeners.push(announcement.listener());
|
||||
}
|
||||
}
|
||||
|
||||
// Announce on all channels
|
||||
for (i, announcer) in announcers.into_iter().enumerate() {
|
||||
announcer.announce(Arc::new(i as i32)).unwrap();
|
||||
}
|
||||
|
||||
// Verify delivery (sampling for performance)
|
||||
for i in (0..all_listeners.len()).step_by(1000) {
|
||||
black_box(all_listeners[i].try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Single announcement to 10K listeners
|
||||
fn bench_one_to_million(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("stress/one_to_ten_thousand");
|
||||
group.sample_size(10);
|
||||
|
||||
group.bench_function("1_announcement_x_10K_listeners", |b| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
let num_listeners = 10_000;
|
||||
|
||||
let mut listeners = Vec::with_capacity(num_listeners);
|
||||
for _ in 0..num_listeners {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(Arc::new(42)).unwrap();
|
||||
|
||||
// Sample retrieval
|
||||
for i in (0..listeners.len()).step_by(1000) {
|
||||
black_box(listeners[i].try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Fully connected network - N announcements × N listeners
|
||||
fn bench_fully_connected(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("stress/fully_connected");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
// N announcements each received by N listeners = N² connections
|
||||
for n in [10, 100, 500].iter() {
|
||||
let total_connections = n * n;
|
||||
group.throughput(Throughput::Elements(total_connections as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
// Create N announcement channels
|
||||
let mut announcements = Vec::with_capacity(n);
|
||||
let mut announcers = Vec::with_capacity(n);
|
||||
|
||||
for _ in 0..n {
|
||||
let (tx, announcement) = Announcement::<Arc<i32>>::new();
|
||||
announcers.push(tx);
|
||||
announcements.push(announcement);
|
||||
}
|
||||
|
||||
// Create N listeners for each announcement (N² total listeners)
|
||||
let mut all_listeners = Vec::with_capacity(n * n);
|
||||
for announcement in &announcements {
|
||||
for _ in 0..n {
|
||||
all_listeners.push(announcement.listener());
|
||||
}
|
||||
}
|
||||
|
||||
// Announce on all channels
|
||||
for (i, announcer) in announcers.into_iter().enumerate() {
|
||||
announcer.announce(Arc::new(i as i32)).unwrap();
|
||||
}
|
||||
|
||||
// Retrieve all values
|
||||
for listener in &all_listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Cascade chain - A triggers B triggers C...
|
||||
fn bench_cascade_chain(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("stress/cascade_chain");
|
||||
|
||||
for chain_length in [10, 100, 500].iter() {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(chain_length),
|
||||
chain_length,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let mut announcements = Vec::with_capacity(n);
|
||||
let mut announcers = Vec::with_capacity(n);
|
||||
|
||||
// Create chain of announcements
|
||||
for _ in 0..n {
|
||||
let (tx, announcement) = Announcement::<Arc<i32>>::new();
|
||||
announcers.push(tx);
|
||||
announcements.push(announcement);
|
||||
}
|
||||
|
||||
// Link chain: each announcement triggers next
|
||||
let listeners: Vec<_> = announcements
|
||||
.iter()
|
||||
.take(n - 1)
|
||||
.map(|a| a.listener())
|
||||
.collect();
|
||||
|
||||
// Trigger cascade
|
||||
for (i, announcer) in announcers.into_iter().enumerate() {
|
||||
announcer.announce(Arc::new(i as i32)).unwrap();
|
||||
}
|
||||
|
||||
// Verify propagation
|
||||
for listener in &listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Benchmark: Cascade tree - A triggers B1, B2, B3... (tree fanout)
|
||||
fn bench_cascade_tree(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("stress/cascade_tree");
|
||||
|
||||
for fanout in [2, 5, 10, 50].iter() {
|
||||
group.throughput(Throughput::Elements(*fanout as u64));
|
||||
group.bench_with_input(BenchmarkId::from_parameter(fanout), fanout, |b, &fanout| {
|
||||
b.iter(|| {
|
||||
// Root announcement
|
||||
let (root_tx, root_announcement) = Announcement::<Arc<i32>>::new();
|
||||
|
||||
// Create fanout child announcements
|
||||
let mut child_announcers = Vec::with_capacity(fanout);
|
||||
let mut child_announcements = Vec::with_capacity(fanout);
|
||||
|
||||
for _ in 0..fanout {
|
||||
let (tx, announcement) = Announcement::<Arc<i32>>::new();
|
||||
child_announcers.push(tx);
|
||||
child_announcements.push(announcement);
|
||||
}
|
||||
|
||||
// Root listener triggers all children
|
||||
let _root_listener = root_announcement.listener();
|
||||
|
||||
// Create listeners for all children
|
||||
let mut child_listeners = Vec::with_capacity(fanout);
|
||||
for announcement in &child_announcements {
|
||||
child_listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
// Trigger cascade
|
||||
root_tx.announce(Arc::new(0)).unwrap();
|
||||
for (i, announcer) in child_announcers.into_iter().enumerate() {
|
||||
announcer.announce(Arc::new(i as i32)).unwrap();
|
||||
}
|
||||
|
||||
// Verify all children received
|
||||
for listener in &child_listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_million_announcements,
|
||||
bench_one_to_million,
|
||||
bench_fully_connected,
|
||||
bench_cascade_chain,
|
||||
bench_cascade_tree,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,284 @@
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::collections::HashMap;
|
||||
use std::hint::black_box;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Small type: 4 bytes
|
||||
fn bench_small_type(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("small_type_i32");
|
||||
|
||||
for n in [1, 10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// Direct Clone
|
||||
group.bench_with_input(BenchmarkId::new("direct_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(42i32).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Arc<i32> Clone
|
||||
group.bench_with_input(BenchmarkId::new("arc_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(Arc::new(42i32)).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// Medium type: 1KB
|
||||
fn bench_medium_type(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("medium_type_1kb");
|
||||
|
||||
for n in [1, 10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// Direct Clone
|
||||
group.bench_with_input(BenchmarkId::new("direct_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce([0u8; 1024]).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Arc Clone
|
||||
group.bench_with_input(BenchmarkId::new("arc_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(Arc::new([0u8; 1024])).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// Large type: 1MB
|
||||
fn bench_large_type(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("large_type_1mb");
|
||||
group.sample_size(10); // Reduce sample size for large data
|
||||
|
||||
for n in [1, 10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// Direct Clone
|
||||
group.bench_with_input(BenchmarkId::new("direct_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(vec![0u8; 1024 * 1024]).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Arc Clone
|
||||
group.bench_with_input(BenchmarkId::new("arc_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(Arc::new(vec![0u8; 1024 * 1024])).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// Complex type: Config with String, Vec, HashMap
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Config {
|
||||
name: String,
|
||||
tags: Vec<String>,
|
||||
settings: HashMap<String, String>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn new() -> Self {
|
||||
let mut settings = HashMap::new();
|
||||
settings.insert("key1".to_string(), "value1".to_string());
|
||||
settings.insert("key2".to_string(), "value2".to_string());
|
||||
settings.insert("key3".to_string(), "value3".to_string());
|
||||
|
||||
Config {
|
||||
name: "MyConfig".to_string(),
|
||||
tags: vec!["tag1".to_string(), "tag2".to_string(), "tag3".to_string()],
|
||||
settings,
|
||||
data: vec![0u8; 1024],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bench_complex_type(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("complex_type_config");
|
||||
|
||||
for n in [1, 10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// Direct Clone
|
||||
group.bench_with_input(BenchmarkId::new("direct_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(Config::new()).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Arc Clone
|
||||
group.bench_with_input(BenchmarkId::new("arc_clone", n), n, |b, &n| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..n {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
tx.announce(Arc::new(Config::new())).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// Comparison benchmark showing the crossover point
|
||||
fn bench_crossover_analysis(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("crossover_analysis");
|
||||
|
||||
// Test various type sizes to find where Arc becomes beneficial
|
||||
let sizes = [4, 8, 16, 32, 64, 128, 256, 512];
|
||||
|
||||
for size in sizes.iter() {
|
||||
group.throughput(Throughput::Bytes(*size as u64));
|
||||
|
||||
// Direct clone with 10 listeners
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("direct_10_listeners", size),
|
||||
size,
|
||||
|b, &size| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..10 {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
let data = vec![0u8; size];
|
||||
tx.announce(data).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Arc clone with 10 listeners
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("arc_10_listeners", size),
|
||||
size,
|
||||
|b, &size| {
|
||||
b.iter(|| {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut listeners = Vec::new();
|
||||
for _ in 0..10 {
|
||||
listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
let data = Arc::new(vec![0u8; size]);
|
||||
tx.announce(data).unwrap();
|
||||
|
||||
for listener in listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_small_type,
|
||||
bench_medium_type,
|
||||
bench_large_type,
|
||||
bench_complex_type,
|
||||
bench_crossover_analysis,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -0,0 +1,237 @@
|
||||
//! Comparison benchmarks against alternative implementations
|
||||
//!
|
||||
//! Compares announcement's oneshot broadcast pattern against:
|
||||
//! - tokio::sync::oneshot (N separate channels)
|
||||
//! - tokio::sync::broadcast (multi-shot channel used for oneshot)
|
||||
//! - async-broadcast (runtime-agnostic broadcast)
|
||||
//! - oneshot_broadcast crate (direct competitor)
|
||||
//!
|
||||
//! Each comparison uses the oneshot pattern: send once, N receivers.
|
||||
|
||||
#![allow(deprecated)]
|
||||
#![allow(unused_must_use)]
|
||||
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::hint::black_box;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Compare: announcement vs tokio::sync::oneshot (N separate channels)
|
||||
///
|
||||
/// Pattern: One announcement to N listeners vs N separate oneshot channels
|
||||
fn bench_vs_tokio_oneshot(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("vs_alternatives/tokio_oneshot");
|
||||
|
||||
for n in [5, 10, 50, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// announcement - O(1) channel, O(N) arc clones
|
||||
group.bench_with_input(BenchmarkId::new("announcement", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (tx, announcement) = Announcement::new();
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let listener = announcement.listener();
|
||||
handles.push(tokio::spawn(async move { listener.listen().await }));
|
||||
}
|
||||
|
||||
tx.announce(Arc::new(42)).unwrap();
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// N oneshots - O(N) channels, O(N) sends
|
||||
group.bench_with_input(BenchmarkId::new("tokio_oneshot", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let mut senders = Vec::with_capacity(n);
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
|
||||
for _ in 0..n {
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
senders.push(tx);
|
||||
handles.push(tokio::spawn(async move { rx.await.unwrap() }));
|
||||
}
|
||||
|
||||
let data = Arc::new(42);
|
||||
for tx in senders {
|
||||
tx.send(Arc::clone(&data)).unwrap();
|
||||
}
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Compare: announcement vs tokio::sync::broadcast (for oneshot pattern)
|
||||
///
|
||||
/// tokio::broadcast is designed for multi-shot but can be used for oneshot
|
||||
fn bench_vs_tokio_broadcast(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("vs_alternatives/tokio_broadcast");
|
||||
|
||||
for n in [1, 10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// announcement - optimized for oneshot
|
||||
group.bench_with_input(BenchmarkId::new("announcement", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let listener = announcement.listener();
|
||||
handles.push(tokio::spawn(async move { listener.listen().await }));
|
||||
}
|
||||
|
||||
announcer.announce(Arc::new(42)).unwrap();
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// tokio::broadcast - designed for multi-shot
|
||||
group.bench_with_input(BenchmarkId::new("tokio_broadcast", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (tx, _) = tokio::sync::broadcast::channel::<Arc<i32>>(1);
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let mut rx = tx.subscribe();
|
||||
handles.push(tokio::spawn(async move { rx.recv().await.unwrap() }));
|
||||
}
|
||||
|
||||
tx.send(Arc::new(42)).unwrap();
|
||||
drop(tx);
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Compare: announcement vs async-broadcast (runtime-agnostic)
|
||||
///
|
||||
/// NOTE: async-broadcast benchmark is commented out due to a bug in the crate
|
||||
/// where broadcast() fails with SendError even with receivers waiting.
|
||||
/// See tests/async_broadcast_race_repro.rs for minimal reproduction.
|
||||
fn bench_vs_async_broadcast(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("vs_alternatives/async_broadcast");
|
||||
|
||||
for n in [1, 10, 100, 500].iter() {
|
||||
group.throughput(Throughput::Elements(*n as u64));
|
||||
|
||||
// announcement
|
||||
group.bench_with_input(BenchmarkId::new("announcement", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let listener = announcement.listener();
|
||||
handles.push(tokio::spawn(async move { listener.listen().await }));
|
||||
}
|
||||
|
||||
announcer.announce(Arc::new(42)).unwrap();
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// async-broadcast - COMMENTED OUT due to crate bug
|
||||
// Bug: broadcast() randomly fails with SendError even when receivers exist
|
||||
// Reproduction: tests/async_broadcast_race_repro.rs
|
||||
/*
|
||||
group.bench_with_input(BenchmarkId::new("async_broadcast", n), n, |b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (tx, _) = async_broadcast::broadcast::<Arc<i32>>(1);
|
||||
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
let mut rx = tx.new_receiver();
|
||||
handles.push(tokio::spawn(async move { rx.recv().await.unwrap() }));
|
||||
}
|
||||
|
||||
tx.broadcast(Arc::new(42)).await.unwrap();
|
||||
|
||||
for h in handles {
|
||||
black_box(h.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Show where tokio::broadcast excels: multiple announcements
|
||||
///
|
||||
/// This is NOT what announcement is designed for - this shows the tradeoffs
|
||||
fn bench_multishot_pattern(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("vs_alternatives/multishot_pattern");
|
||||
|
||||
// announcement - must create new channel each time
|
||||
group.bench_function("announcement_recreate", |b| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
for i in 0..10 {
|
||||
let (announcer, announcement) = Announcement::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let handle = tokio::spawn(async move { listener.listen().await });
|
||||
announcer.announce(Arc::new(i)).unwrap();
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// tokio::broadcast - designed for this
|
||||
group.bench_function("tokio_broadcast_multishot", |b| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (tx, _) = tokio::sync::broadcast::channel::<Arc<i32>>(10);
|
||||
let mut rx = tx.subscribe();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
for _ in 0..10 {
|
||||
black_box(rx.recv().await.unwrap());
|
||||
}
|
||||
});
|
||||
|
||||
for i in 0..10 {
|
||||
tx.send(Arc::new(i)).unwrap();
|
||||
}
|
||||
|
||||
handle.await.unwrap();
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_vs_tokio_oneshot,
|
||||
bench_vs_tokio_broadcast,
|
||||
bench_vs_async_broadcast,
|
||||
bench_multishot_pattern,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
Reference in new issue
Block a user