use std::hint::black_box;
use std::num::NonZeroUsize;
use std::sync::mpsc;
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
use dinah::runtime::{Executor, Scheduler, TaskQueue};
const SAMPLES: usize = 7;
const TASK_SIZES: &[usize] = &[1, 16, 64, 256];
enum PoolMessage {
Run(Box<dyn FnOnce() + Send + 'static>),
Shutdown,
}
struct PersistentPool {
senders: Vec<mpsc::Sender<PoolMessage>>,
workers: Vec<JoinHandle<()>>,
}
impl PersistentPool {
fn new(worker_count: usize) -> Self {
let mut senders = Vec::with_capacity(worker_count);
let mut workers = Vec::with_capacity(worker_count);
for _ in 0..worker_count {
let (sender, receiver) = mpsc::channel::<PoolMessage>();
senders.push(sender);
workers.push(thread::spawn(move || {
while let Ok(PoolMessage::Run(job)) = receiver.recv() {
job();
}
}));
}
Self { senders, workers }
}
fn run_tasks(
&self,
task_count: usize,
worker_count: usize,
task: fn() -> Result<u64, &'static str>,
) {
let (done_sender, done_receiver) = mpsc::channel();
let mut completed = 0;
while completed < task_count {
let batch_size = worker_count.min(task_count - completed);
for worker_index in 0..batch_size {
let done_sender = done_sender.clone();
self.senders[worker_index]
.send(PoolMessage::Run(Box::new(move || {
black_box(task().expect("benchmark pool task succeeds"));
done_sender
.send(())
.expect("benchmark completion receiver exists");
})))
.expect("benchmark pool is accepting work");
}
for _ in 0..batch_size {
done_receiver
.recv()
.expect("every benchmark pool task completes");
}
completed += batch_size;
}
drop(done_sender);
}
}
impl Drop for PersistentPool {
fn drop(&mut self) {
for sender in &self.senders {
let _ = sender.send(PoolMessage::Shutdown);
}
for worker in self.workers.drain(..) {
worker.join().expect("benchmark pool worker exits cleanly");
}
}
}
fn noop_task() -> Result<u64, &'static str> {
Ok(black_box(1))
}
fn cpu_task() -> Result<u64, &'static str> {
let mut value = black_box(1_u64);
for _ in 0..1_000_000 {
value = value
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
}
Ok(black_box(value))
}
fn report(name: &str, task_count: usize, samples: &[Duration]) {
let mut nanos: Vec<u128> = samples.iter().map(Duration::as_nanos).collect();
nanos.sort_unstable();
let median_ns = nanos[nanos.len() / 2];
let min_ns = nanos[0];
let max_ns = nanos[nanos.len() - 1];
let operations = task_count.max(1) as u128;
let ns_per_task = median_ns as f64 / operations as f64;
let tasks_per_second = if median_ns == 0 {
0.0
} else {
(operations as f64 * 1_000_000_000.0) / median_ns as f64
};
println!(
"case={name} tasks={task_count} samples={SAMPLES} min_total_ns={min_ns} median_total_ns={median_ns} max_total_ns={max_ns} ns_per_task={ns_per_task:.3} tasks_per_second={tasks_per_second:.0}"
);
}
fn measure<F>(mut operation: F) -> Vec<Duration>
where
F: FnMut(),
{
let mut samples = Vec::with_capacity(SAMPLES);
for _ in 0..SAMPLES {
let start = Instant::now();
operation();
samples.push(start.elapsed());
}
samples
}
fn main() {
println!("dinah runtime benchmark; build=release; statistic=median; samples={SAMPLES}");
println!("Note: results are machine-specific observations, not performance guarantees.");
let executor = Executor::new();
let direct_iterations = 100_000usize;
let direct_samples = measure(|| {
for _ in 0..direct_iterations {
black_box(
executor
.execute(noop_task)
.expect("benchmark task succeeds"),
);
}
});
report("executor_noop", direct_iterations, &direct_samples);
let scheduler = Scheduler::new();
let immediate_iterations = 100_000usize;
let immediate_samples = measure(|| {
for _ in 0..immediate_iterations {
black_box(
scheduler
.schedule(noop_task)
.expect("immediate scheduler accepts work")
.expect("benchmark task succeeds"),
);
}
});
report(
"scheduler_immediate_noop",
immediate_iterations,
&immediate_samples,
);
for &task_count in TASK_SIZES {
let sequential_samples = measure(|| {
let capacity = NonZeroUsize::new(task_count).expect("task size is nonzero");
let mut queue = TaskQueue::new(capacity);
for _ in 0..task_count {
queue
.try_push(noop_task as fn() -> Result<u64, &'static str>)
.expect("queue capacity matches task count");
}
let results = scheduler.run_until_empty(&mut queue);
black_box(results);
assert!(queue.is_empty());
});
report(
"scheduler_queue_sequential",
task_count,
&sequential_samples,
);
}
for &worker_count in &[2usize, 4] {
let startup_samples = measure(|| {
let pool = PersistentPool::new(worker_count);
black_box(&pool);
drop(pool);
});
report(
if worker_count == 2 {
"persistent_pool_startup_shutdown_2_workers"
} else {
"persistent_pool_startup_shutdown_4_workers"
},
1,
&startup_samples,
);
}
for &task_count in &[16usize, 64, 256] {
let sequential_cpu_samples = measure(|| {
let capacity = NonZeroUsize::new(task_count).expect("task size is nonzero");
let mut queue = TaskQueue::new(capacity);
for _ in 0..task_count {
queue
.try_push(cpu_task as fn() -> Result<u64, &'static str>)
.expect("queue capacity matches task count");
}
let results = scheduler.run_until_empty(&mut queue);
black_box(results);
assert!(queue.is_empty());
});
report(
"scheduler_queue_sequential_cpu_1m",
task_count,
&sequential_cpu_samples,
);
for &worker_count in &[2usize, 4] {
let parallel_noop_samples = measure(|| {
let capacity = NonZeroUsize::new(task_count).expect("task size is nonzero");
let mut queue = TaskQueue::new(capacity);
for _ in 0..task_count {
queue
.try_push(noop_task as fn() -> Result<u64, &'static str>)
.expect("queue capacity matches task count");
}
let workers = NonZeroUsize::new(worker_count).expect("worker count is nonzero");
let results = scheduler.run_parallel(&mut queue, workers);
black_box(results);
assert!(queue.is_empty());
});
report(
if worker_count == 2 {
"scheduler_queue_parallel_noop_2_workers"
} else {
"scheduler_queue_parallel_noop_4_workers"
},
task_count,
¶llel_noop_samples,
);
}
for &worker_count in &[2usize, 4] {
let lifecycle_samples = measure(|| {
let pool = PersistentPool::new(worker_count);
pool.run_tasks(task_count, worker_count, noop_task);
drop(pool);
});
report(
if worker_count == 2 {
"persistent_pool_noop_lifecycle_2_workers"
} else {
"persistent_pool_noop_lifecycle_4_workers"
},
task_count,
&lifecycle_samples,
);
let pool = PersistentPool::new(worker_count);
let noop_pool_samples = measure(|| pool.run_tasks(task_count, worker_count, noop_task));
report(
if worker_count == 2 {
"persistent_pool_noop_2_workers"
} else {
"persistent_pool_noop_4_workers"
},
task_count,
&noop_pool_samples,
);
let cpu_pool_samples = measure(|| pool.run_tasks(task_count, worker_count, cpu_task));
report(
if worker_count == 2 {
"persistent_pool_cpu_1m_2_workers"
} else {
"persistent_pool_cpu_1m_4_workers"
},
task_count,
&cpu_pool_samples,
);
drop(pool);
}
for &worker_count in &[2usize, 4] {
let parallel_samples = measure(|| {
let capacity = NonZeroUsize::new(task_count).expect("task size is nonzero");
let mut queue = TaskQueue::new(capacity);
for _ in 0..task_count {
queue
.try_push(cpu_task as fn() -> Result<u64, &'static str>)
.expect("queue capacity matches task count");
}
let workers = NonZeroUsize::new(worker_count).expect("worker count is nonzero");
let results = scheduler.run_parallel(&mut queue, workers);
black_box(results);
assert!(queue.is_empty());
});
report(
if worker_count == 2 {
"scheduler_queue_parallel_cpu_1m_2_workers"
} else {
"scheduler_queue_parallel_cpu_1m_4_workers"
},
task_count,
¶llel_samples,
);
}
}
}