bheisler / criterion.rs

Statistics-driven benchmarking library for Rust
Apache License 2.0
4.52k stars 301 forks source link

AsyncBencher::iter_batched and iter_batched_ref should allow for async setup #751

Open shepmaster opened 8 months ago

shepmaster commented 8 months ago

Right now, the setup closures return a synchronous value, not a future, disallowing setup from being async.

Additionally, iter_batched wraps the call to setup in a block_on call:

https://github.com/bheisler/criterion.rs/blob/b913e232edd98780961ecfbae836ec77ede49259/src/bencher.rs#L622-L631

This means that you can't use block_on in the closure yourself as that would create a nested runtime:

use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use tokio::runtime::Builder;

pub fn criterion_benchmark(c: &mut Criterion) {
    let rt = Builder::new_multi_thread().enable_all().build().unwrap();
    c.bench_function("demo", |b| {
        b.to_async(&rt).iter_batched(
            || rt.block_on(async {}),
            |_| async {},
            BatchSize::SmallInput,
        );
    });
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
thread 'main' panicked at ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.35.1/src/runtime/scheduler/multi_thread/mod.rs:86:9:

Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.

Our current workaround is to use iter_custom instead.

vnermolaev commented 5 months ago

Indeed, I have faced a similar and more extended version of this problem.

I came up with the following workaround for my particular case:

fn setup(rt: &runtime::Runtime) -> u64 {
    rt.block_on(async {
        // do async stuff
        tokio::time::sleep(Duration::from_millis(100)).await;
        1
    })
}

async fn test() -> u64 {
    1
}

pub fn criterion_benchmark(c: &mut Criterion) {
    let rt = runtime::Builder::new_multi_thread()
        .enable_all()
        .build()
        .unwrap();

    let setup = setup(&rt);

    c.bench_function("demo", |b| {
        b.to_async(&rt)
            .iter_batched(|| setup.clone(), |_| async {}, BatchSize::SmallInput);
    });
}