From af277e21733948df122ffc210343c9b54cade7a9 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Tue, 18 Aug 2026 01:57:02 +0900 Subject: test(process-executor): benchmark execute_async with criterion The `single` group compares the argv form against the string form, which goes through an extra `/bin/sh -c`; the `concurrent` group varies the job count around `max_jobs` so the semaphore throttle is visible as a throughput knee. `execute_async`'s futures are `!Send`, so they run on a current-thread runtime and overlap only through `join_all` within a single task. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe/Cargo.toml | 5 ++ crates/shirabe/benches/process_executor.rs | 92 ++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 crates/shirabe/benches/process_executor.rs (limited to 'crates/shirabe') diff --git a/crates/shirabe/Cargo.toml b/crates/shirabe/Cargo.toml index fb58f859..d3e425b2 100644 --- a/crates/shirabe/Cargo.toml +++ b/crates/shirabe/Cargo.toml @@ -40,9 +40,14 @@ tracing-subscriber.workspace = true url.workspace = true [dev-dependencies] +criterion.workspace = true mockall.workspace = true serial_test.workspace = true tempfile.workspace = true +[[bench]] +name = "process_executor" +harness = false + [lints] workspace = true diff --git a/crates/shirabe/benches/process_executor.rs b/crates/shirabe/benches/process_executor.rs new file mode 100644 index 00000000..8cd00095 --- /dev/null +++ b/crates/shirabe/benches/process_executor.rs @@ -0,0 +1,92 @@ +//! Benchmarks for `ProcessExecutor::execute_async`. + +use criterion::BatchSize; +use criterion::BenchmarkId; +use criterion::Criterion; +use criterion::Throughput; +use criterion::criterion_group; +use criterion::criterion_main; +use shirabe::util::process_executor::ProcessExecutor; + +/// Pinned so the measurement does not depend on `COMPOSER_MAX_PARALLEL_PROCESSES`, which +/// `ProcessExecutor::new` otherwise reads to size the semaphore. +const MAX_JOBS: i64 = 10; + +/// The cheapest child that still goes through the whole spawn path. +const COMMAND: &[&str; 1] = &["true"]; + +/// `execute_async`'s futures are `!Send` (the io handle is an `Rc`), so they are driven by +/// `block_on` on a current-thread runtime rather than spawned onto a worker pool. Concurrency +/// within a case therefore comes from joining futures inside the single task. +fn runtime() -> tokio::runtime::Runtime { + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("failed to build the benchmark runtime") +} + +fn executor() -> ProcessExecutor { + let mut process_executor = ProcessExecutor::new(None); + process_executor.enable_async(); + process_executor.set_max_jobs(MAX_JOBS); + process_executor +} + +fn bench_single(c: &mut Criterion) { + let runtime = runtime(); + let mut group = c.benchmark_group("execute_async/single"); + + group.bench_function("argv", |b| { + b.to_async(&runtime).iter_batched( + executor, + |mut process_executor| async move { + let process = process_executor.execute_async(COMMAND, None); + process.await.expect("the child failed to run") + }, + BatchSize::SmallInput, + ); + }); + + group.bench_function("shell", |b| { + b.to_async(&runtime).iter_batched( + executor, + |mut process_executor| async move { + let process = process_executor.execute_async("true", None); + process.await.expect("the child failed to run") + }, + BatchSize::SmallInput, + ); + }); + + group.finish(); +} + +fn bench_concurrent(c: &mut Criterion) { + let runtime = runtime(); + let mut group = c.benchmark_group("execute_async/concurrent"); + + for jobs in [1_u64, MAX_JOBS as u64, MAX_JOBS as u64 * 2] { + group.throughput(Throughput::Elements(jobs)); + group.bench_with_input(BenchmarkId::from_parameter(jobs), &jobs, |b, &jobs| { + b.to_async(&runtime).iter_batched( + executor, + |mut process_executor| async move { + let processes: Vec<_> = (0..jobs) + .map(|_| process_executor.execute_async(COMMAND, None)) + .collect(); + futures::future::join_all(processes) + .await + .into_iter() + .map(|process| process.expect("the child failed to run")) + .collect::>() + }, + BatchSize::SmallInput, + ); + }); + } + + group.finish(); +} + +criterion_group!(benches, bench_single, bench_concurrent); +criterion_main!(benches); -- cgit v1.3.1-4-g156e