use std::{
future::{Future, poll_fn},
pin::Pin,
task::Poll,
time::Duration,
};
use criterion::{Criterion, Throughput, criterion_group};
use executor::{ZruntimeExecutor, runtime_handle};
use zruntime::{Shared, Sleep};
#[cfg(unix)]
#[path = "common/cpus.rs"]
mod cpus;
#[path = "common/executor.rs"]
mod executor;
fn timer_benches(c: &mut Criterion) {
let runtime = runtime_handle();
let mut group = c.benchmark_group("timer");
group.sample_size(20);
group.bench_function("sleep-1ms", |b| {
b.to_async(ZruntimeExecutor)
.iter(|| async { runtime.sleep(TIMER_DURATION).await });
});
group.throughput(Throughput::Elements(100));
group.bench_function("100-timers", |b| {
b.to_async(ZruntimeExecutor).iter(|| async {
let sleeps: Vec<_> = (0..100).map(|_| runtime.sleep(TIMER_DURATION)).collect();
join_all(sleeps).await
});
});
group.finish();
}
const TIMER_DURATION: Duration = Duration::from_millis(1);
async fn join_all(mut sleeps: Vec<Sleep<Shared>>) {
poll_fn(move |cx| {
sleeps.retain_mut(|sleep| Pin::new(sleep).poll(cx).is_pending());
if sleeps.is_empty() {
Poll::Ready(())
} else {
Poll::Pending
}
})
.await
}
#[cfg(unix)]
mod unix {
use std::{
future::poll_fn,
hint::black_box,
io::{Read, Write},
os::unix::net::UnixStream,
sync::Arc,
thread,
};
use criterion::{BatchSize, Criterion, Throughput};
use futures::future;
use zruntime::{Interest, Registration, Shared, SharedRuntime};
use super::{ZruntimeExecutor, cpus::Cpus, runtime_handle};
const BIG: usize = 1024 * 1024;
const IN_FLIGHT: usize = 1000;
const CHUNK: usize = 64 * 1024;
const REGISTER_BATCH: u64 = 64;
const IDLE: usize = 256;
pub(super) fn io_benches(c: &mut Criterion) {
let runtime = runtime_handle();
let mut group = c.benchmark_group("io");
group.bench_function("register-and-drop", |b| {
let runtime = &runtime;
b.to_async(ZruntimeExecutor).iter_batched(
pair,
|(local, far)| async move {
let registrations = (register(runtime, &local), register(runtime, &far));
drop(black_box(registrations));
},
BatchSize::NumIterations(REGISTER_BATCH),
);
});
{
let (local, far) = pair();
let registration = register(&runtime, &local);
let far_registration = register(&runtime, &far);
let _echo = runtime.spawn("bench echo", echo_forever(far_registration, far));
group.bench_function("roundtrip", |b| {
b.to_async(ZruntimeExecutor).iter(|| async {
write_all(®istration, &local, &[7]).await;
let mut response = [0u8; 1];
read_exact(®istration, &local, &mut response).await;
black_box(response);
});
});
}
{
let _idle: Vec<_> = (0..IDLE)
.map(|_| {
let (near, far) = pair();
let registration = runtime.register(near).unwrap();
let waiting = runtime.spawn("bench idle", async move {
registration.ready(Interest::Readable).await.unwrap();
});
(waiting, far)
})
.collect();
let (local, far) = pair();
let registration = register(&runtime, &local);
let far_registration = register(&runtime, &far);
let _echo = runtime.spawn("bench echo", echo_forever(far_registration, far));
group.bench_function("roundtrip-among-256-idle", |b| {
b.to_async(ZruntimeExecutor).iter(|| async {
write_all(®istration, &local, &[7]).await;
let mut response = [0u8; 1];
read_exact(®istration, &local, &mut response).await;
black_box(response);
});
});
}
group.sample_size(10);
group.throughput(Throughput::Bytes(BIG as u64));
{
let (local, far) = pair();
let registration = register(&runtime, &local);
let far_registration = register(&runtime, &far);
let _echo = runtime.spawn("bench echo", echo_forever(far_registration, far));
let body = vec![7u8; BIG];
group.bench_function("1MiB", |b| {
b.to_async(ZruntimeExecutor).iter(|| async {
let mut response = vec![0u8; BIG];
future::join(
write_all(®istration, &local, &body),
read_exact(®istration, &local, &mut response),
)
.await;
black_box(response);
});
});
}
group.finish();
}
pub(super) fn cross_thread_benches(c: &mut Criterion) {
let mut group = c.benchmark_group("cross-thread");
group.sample_size(10);
let cpus = Cpus::allowed();
let far_cpu = cpus.nth(1);
let _pinned = cpus.nth(0).pin();
let (local, far) = pair();
let far_thread = thread::spawn(move || {
let _pinned = far_cpu.pin();
zruntime::block_on(async move {
let runtime = SharedRuntime::current().expect("a runtime for this thread");
let far_registration = register(&runtime, &far);
echo_forever(far_registration, far).await;
});
});
let runtime = runtime_handle();
let local = Arc::new(Watched {
registration: register(&runtime, &local),
stream: local,
});
group.bench_function("roundtrip", |b| {
b.to_async(ZruntimeExecutor).iter(|| async {
write_all(&local.registration, &local.stream, &[7]).await;
let mut response = [0u8; 1];
read_exact(&local.registration, &local.stream, &mut response).await;
black_box(response);
});
});
group.throughput(Throughput::Elements(IN_FLIGHT as u64));
group.bench_function("1000-in-flight", |b| {
b.to_async(ZruntimeExecutor).iter(|| async {
let writer = {
let local = local.clone();
runtime.spawn("bench writer", async move {
for request in 0..IN_FLIGHT as u32 {
write_all(&local.registration, &local.stream, &request.to_le_bytes())
.await;
}
})
};
let mut replies = vec![0u8; IN_FLIGHT * size_of::<u32>()];
read_exact(&local.registration, &local.stream, &mut replies).await;
writer.await.unwrap();
black_box(replies);
});
});
group.finish();
drop(local);
far_thread.join().unwrap();
}
struct Watched {
registration: Registration<Shared>,
stream: UnixStream,
}
fn register(runtime: &SharedRuntime, stream: &UnixStream) -> Registration<Shared> {
runtime.register(stream.try_clone().unwrap()).unwrap()
}
fn pair() -> (UnixStream, UnixStream) {
let (a, b) = UnixStream::pair().unwrap();
for stream in [&a, &b] {
stream.set_nonblocking(true).unwrap();
}
(a, b)
}
async fn echo_forever(registration: Registration<Shared>, far: UnixStream) {
let mut chunk = [0u8; CHUNK];
loop {
let read = read_some(®istration, &far, &mut chunk).await;
if read == 0 {
return;
}
write_all(®istration, &far, &chunk[..read]).await;
}
}
async fn read_some(
registration: &Registration<Shared>,
mut stream: &UnixStream,
buf: &mut [u8],
) -> usize {
poll_fn(|cx| registration.poll_io(cx, Interest::Readable, || stream.read(buf)))
.await
.unwrap()
}
async fn read_exact(
registration: &Registration<Shared>,
stream: &UnixStream,
mut buf: &mut [u8],
) {
while !buf.is_empty() {
let read = read_some(registration, stream, buf).await;
assert_ne!(read, 0, "the peer closed the connection early");
buf = &mut buf[read..];
}
}
async fn write_all(
registration: &Registration<Shared>,
mut stream: &UnixStream,
mut buf: &[u8],
) {
while !buf.is_empty() {
let written =
poll_fn(|cx| registration.poll_io(cx, Interest::Writable, || stream.write(buf)))
.await
.unwrap();
buf = &buf[written..];
}
}
}
#[cfg(unix)]
criterion_group!(
benches,
timer_benches,
unix::io_benches,
unix::cross_thread_benches
);
#[cfg(not(unix))]
criterion_group!(benches, timer_benches);
criterion::criterion_main!(benches);