Skip to content

Commit 7d5671f

Browse files
--wip-- [skip ci]
1 parent 418a223 commit 7d5671f

3 files changed

Lines changed: 125 additions & 115 deletions

File tree

Lines changed: 23 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,11 @@
11
use codspeed_criterion_compat::{criterion_group, Criterion, IterManualOptions};
22

3-
fn iter_manual_basic(c: &mut Criterion) {
4-
// rounds=5 is below criterion's sample_size >= 10 floor — proves
5-
// iter_manual fully bypasses the adaptive outer sampler.
6-
c.bench_function("iter_manual_basic", |b| {
7-
b.iter_manual(IterManualOptions::new(5, 100), || {
8-
let mut s = 0u64;
9-
for i in 0..32 {
10-
s = s.wrapping_add(i);
11-
}
12-
s
13-
});
14-
});
15-
}
16-
173
fn iter_manual_with_setup(c: &mut Criterion) {
4+
// rounds=3 is below criterion's sample_size >= 10 floor — proves
5+
// iter_manual fully bypasses the adaptive outer sampler.
186
c.bench_function("iter_manual_setup", |b| {
197
b.iter_manual_setup(
20-
IterManualOptions {
21-
rounds: 3,
22-
iterations: 100,
23-
warmup_rounds: 2,
24-
},
8+
IterManualOptions::new().rounds(3).iters(100).warmup(2),
259
|| (0u64..256).collect::<Vec<_>>(),
2610
|input| input.iter().copied().sum::<u64>(),
2711
);
@@ -31,30 +15,31 @@ fn iter_manual_with_setup(c: &mut Criterion) {
3115
fn iter_manual_with_teardown(c: &mut Criterion) {
3216
c.bench_function("iter_manual_setup_teardown", |b| {
3317
b.iter_manual_setup_teardown(
34-
IterManualOptions {
35-
rounds: 7,
36-
iterations: 50,
37-
warmup_rounds: 1,
38-
},
18+
IterManualOptions::new().rounds(7).iters(50).warmup(1),
3919
|| (0u64..128).collect::<Vec<_>>(),
4020
|input| input.iter().copied().sum::<u64>(),
4121
drop,
4222
);
4323
});
4424
}
4525

46-
#[cfg(feature = "async_futures")]
47-
fn iter_manual_async_basic(c: &mut Criterion) {
48-
use codspeed_criterion_compat::async_executor::FuturesExecutor;
49-
c.bench_function("iter_manual_async_basic", |b| {
50-
b.to_async(FuturesExecutor)
51-
.iter_manual(IterManualOptions::new(5, 100), || async {
52-
let mut s = 0u64;
53-
for i in 0..32 {
54-
s = s.wrapping_add(i);
26+
// Setup body deliberately does a chunk of work so it stands out in flamegraphs.
27+
// The measured region should NOT include this work.
28+
fn iter_manual_heavy_setup(c: &mut Criterion) {
29+
c.bench_function("iter_manual_heavy_setup", |b| {
30+
b.iter_manual_setup(
31+
IterManualOptions::new().rounds(3).iters(50).warmup(1),
32+
|| {
33+
// Heavy setup: build a large vector. Should NOT appear inside
34+
// the measured region of the flamegraph.
35+
let mut v = Vec::with_capacity(10_000);
36+
for i in 0..10_000u64 {
37+
v.push(i.wrapping_mul(31));
5538
}
56-
s
57-
});
39+
v
40+
},
41+
|input| input.iter().copied().sum::<u64>(),
42+
);
5843
});
5944
}
6045

@@ -63,11 +48,7 @@ fn iter_manual_async_with_setup(c: &mut Criterion) {
6348
use codspeed_criterion_compat::async_executor::FuturesExecutor;
6449
c.bench_function("iter_manual_async_setup", |b| {
6550
b.to_async(FuturesExecutor).iter_manual_setup(
66-
IterManualOptions {
67-
rounds: 3,
68-
iterations: 100,
69-
warmup_rounds: 2,
70-
},
51+
IterManualOptions::new().rounds(3).iters(100).warmup(2),
7152
|| (0u64..256).collect::<Vec<_>>(),
7253
|input| {
7354
let sum = input.iter().copied().sum::<u64>();
@@ -82,11 +63,7 @@ fn iter_manual_async_with_teardown(c: &mut Criterion) {
8263
use codspeed_criterion_compat::async_executor::FuturesExecutor;
8364
c.bench_function("iter_manual_async_setup_teardown", |b| {
8465
b.to_async(FuturesExecutor).iter_manual_setup_teardown(
85-
IterManualOptions {
86-
rounds: 7,
87-
iterations: 50,
88-
warmup_rounds: 1,
89-
},
66+
IterManualOptions::new().rounds(7).iters(50).warmup(1),
9067
|| (0u64..128).collect::<Vec<_>>(),
9168
|input| {
9269
let sum = input.iter().copied().sum::<u64>();
@@ -97,19 +74,16 @@ fn iter_manual_async_with_teardown(c: &mut Criterion) {
9774
});
9875
}
9976

100-
#[cfg(not(feature = "async_futures"))]
101-
fn iter_manual_async_basic(_c: &mut Criterion) {}
10277
#[cfg(not(feature = "async_futures"))]
10378
fn iter_manual_async_with_setup(_c: &mut Criterion) {}
10479
#[cfg(not(feature = "async_futures"))]
10580
fn iter_manual_async_with_teardown(_c: &mut Criterion) {}
10681

10782
criterion_group!(
10883
benches,
109-
iter_manual_basic,
11084
iter_manual_with_setup,
11185
iter_manual_with_teardown,
112-
iter_manual_async_basic,
86+
iter_manual_heavy_setup,
11387
iter_manual_async_with_setup,
11488
iter_manual_async_with_teardown,
11589
);

crates/criterion_compat/criterion_fork/src/codspeed_iter_manual.rs

Lines changed: 102 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! CodSpeed addition: manual control over benchmark iteration counts.
22
//!
3-
//! `iter_manual*` lets the user pin down the exact number of measurement
3+
//! `iter_manual_setup*` lets the user pin down the exact number of measurement
44
//! rounds, inner iterations per round, and warmup rounds. It bypasses
55
//! Criterion's adaptive sampler entirely — see `routine.rs::sample` for the
66
//! short-circuit.
77
8-
use std::time::Instant;
8+
use std::time::{Duration, Instant};
99

1010
use codspeed::instrument_hooks::InstrumentHooks;
1111

@@ -20,28 +20,55 @@ use crate::Bencher;
2020
#[cfg(feature = "async")]
2121
use std::future::Future;
2222

23-
/// Options for the [`iter_manual`](Bencher::iter_manual) family.
23+
/// Options for the [`iter_manual_setup`](Bencher::iter_manual_setup) family.
24+
///
25+
/// Built with a consuming-self builder to match the rest of criterion's
26+
/// configuration APIs (e.g. [`Criterion`](crate::Criterion)).
2427
#[derive(Debug, Clone, Copy)]
2528
pub struct IterManualOptions {
26-
/// Number of measurement rounds (each produces one sample).
27-
pub rounds: u64,
28-
/// Number of routine invocations inside the measured region of each round.
29-
pub iterations: u64,
30-
/// Number of unmeasured warmup rounds run before measurement starts.
31-
pub warmup_rounds: u64,
29+
rounds: u64,
30+
iterations: u64,
31+
warmup_iterations: u64,
3232
}
3333

34-
impl IterManualOptions {
35-
/// Build options with the given rounds and iterations; `warmup_rounds` defaults to 0.
36-
pub fn new(rounds: u64, iterations: u64) -> Self {
34+
impl Default for IterManualOptions {
35+
fn default() -> Self {
3736
Self {
38-
rounds,
39-
iterations,
40-
warmup_rounds: 0,
37+
rounds: 1,
38+
iterations: 1,
39+
warmup_iterations: 0,
4140
}
4241
}
4342
}
4443

44+
impl IterManualOptions {
45+
/// Start with defaults: 1 round, 1 iteration per round, 0 warmup rounds.
46+
pub fn new() -> Self {
47+
Self::default()
48+
}
49+
50+
/// Number of measurement rounds (each produces one sample).
51+
#[must_use]
52+
pub fn rounds(mut self, rounds: u64) -> Self {
53+
self.rounds = rounds;
54+
self
55+
}
56+
57+
/// Number of routine invocations inside a measurement round.
58+
#[must_use]
59+
pub fn iters(mut self, iterations: u64) -> Self {
60+
self.iterations = iterations;
61+
self
62+
}
63+
64+
/// Number of unmeasured warmup iterations run before measurement starts.
65+
#[must_use]
66+
pub fn warmup(mut self, warmup_iterations: u64) -> Self {
67+
self.warmup_iterations = warmup_iterations;
68+
self
69+
}
70+
}
71+
4572
/// Captured output of a manual run. Stored on the `Bencher` and read by
4673
/// `routine.rs::sample` to short-circuit the adaptive sampler.
4774
pub(crate) struct ManualMeasurement {
@@ -54,32 +81,40 @@ pub(crate) struct ManualMeasurement {
5481

5582
impl<'a, M: Measurement> Bencher<'a, M> {
5683
/// Run `routine` exactly `opts.iterations` times inside each of `opts.rounds`
57-
/// measurement rounds, optionally preceded by `opts.warmup_rounds` unmeasured rounds.
84+
/// measurement rounds, with a `setup` closure producing fresh input for
85+
/// each round (outside the measured region). Optionally preceded by
86+
/// `opts.warmup_rounds` unmeasured rounds.
5887
///
5988
/// Criterion's adaptive sampler is bypassed for this benchmark.
6089
#[inline(never)]
61-
pub fn iter_manual<O, R>(&mut self, opts: IterManualOptions, mut routine: R)
62-
where
63-
R: FnMut() -> O,
64-
{
65-
self.iter_manual_setup_teardown(opts, || (), |_| routine(), |_| ());
66-
}
67-
68-
/// Like [`iter_manual`](Self::iter_manual), with a `setup` closure producing
69-
/// fresh input for each round. The routine borrows the input mutably.
70-
#[inline(never)]
7190
pub fn iter_manual_setup<I, O, S, R>(&mut self, opts: IterManualOptions, setup: S, routine: R)
7291
where
7392
S: FnMut() -> I,
7493
R: FnMut(&mut I) -> O,
7594
{
76-
self.iter_manual_setup_teardown(opts, setup, routine, |_| ());
95+
self.__codspeed_root_frame__iter_manual_setup_teardown(opts, setup, routine, |_| ());
7796
}
7897

7998
/// Like [`iter_manual_setup`](Self::iter_manual_setup), with a `teardown`
8099
/// closure called after each round, outside the measured region.
81100
#[inline(never)]
82101
pub fn iter_manual_setup_teardown<I, O, S, R, T>(
102+
&mut self,
103+
opts: IterManualOptions,
104+
setup: S,
105+
routine: R,
106+
teardown: T,
107+
) where
108+
S: FnMut() -> I,
109+
R: FnMut(&mut I) -> O,
110+
T: FnMut(I),
111+
{
112+
self.__codspeed_root_frame__iter_manual_setup_teardown(opts, setup, routine, teardown);
113+
}
114+
115+
#[inline(never)]
116+
#[allow(missing_docs, non_snake_case)]
117+
pub fn __codspeed_root_frame__iter_manual_setup_teardown<I, O, S, R, T>(
83118
&mut self,
84119
opts: IterManualOptions,
85120
mut setup: S,
@@ -92,10 +127,9 @@ impl<'a, M: Measurement> Bencher<'a, M> {
92127
{
93128
self.iterated = true;
94129

95-
let bench_start = InstrumentHooks::current_timestamp();
96-
let time_start = Instant::now();
130+
let mut elapsed_time = Duration::ZERO;
97131

98-
for _ in 0..opts.warmup_rounds {
132+
for _ in 0..opts.warmup_iterations {
99133
let mut input = black_box(setup());
100134
for _ in 0..opts.iterations {
101135
black_box(routine(&mut input));
@@ -106,18 +140,23 @@ impl<'a, M: Measurement> Bencher<'a, M> {
106140
let mut samples = Vec::with_capacity(opts.rounds as usize);
107141
for _ in 0..opts.rounds {
108142
let mut input = black_box(setup());
143+
144+
let bench_start = InstrumentHooks::current_timestamp();
145+
let round_start = Instant::now();
109146
let start = self.measurement.start();
110147
for _ in 0..opts.iterations {
111148
black_box(routine(&mut input));
112149
}
113150
let value = self.measurement.end(start);
151+
elapsed_time += round_start.elapsed();
152+
let bench_end = InstrumentHooks::current_timestamp();
153+
InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end);
154+
114155
teardown(input);
115156
samples.push(self.measurement.to_f64(&value));
116157
}
117158

118-
self.elapsed_time = time_start.elapsed();
119-
let bench_end = InstrumentHooks::current_timestamp();
120-
InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end);
159+
self.elapsed_time = elapsed_time;
121160

122161
self.codspeed_manual = Some(ManualMeasurement {
123162
samples,
@@ -128,16 +167,6 @@ impl<'a, M: Measurement> Bencher<'a, M> {
128167

129168
#[cfg(feature = "async")]
130169
impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> {
131-
/// Async/await variant of [`Bencher::iter_manual`].
132-
#[inline(never)]
133-
pub fn iter_manual<O, R, F>(&mut self, opts: IterManualOptions, mut routine: R)
134-
where
135-
R: FnMut() -> F,
136-
F: Future<Output = O>,
137-
{
138-
self.iter_manual_setup_teardown(opts, || (), |_| routine(), |_| std::future::ready(()));
139-
}
140-
141170
/// Async/await variant of [`Bencher::iter_manual_setup`].
142171
#[inline(never)]
143172
pub fn iter_manual_setup<I, O, S, R, F>(
@@ -150,12 +179,32 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> {
150179
R: FnMut(&mut I) -> F,
151180
F: Future<Output = O>,
152181
{
153-
self.iter_manual_setup_teardown(opts, setup, routine, |_| std::future::ready(()));
182+
self.__codspeed_root_frame__iter_manual_setup_teardown(opts, setup, routine, |_| {
183+
std::future::ready(())
184+
});
154185
}
155186

156187
/// Async/await variant of [`Bencher::iter_manual_setup_teardown`].
157188
#[inline(never)]
158189
pub fn iter_manual_setup_teardown<I, O, S, R, T, RF, TF>(
190+
&mut self,
191+
opts: IterManualOptions,
192+
setup: S,
193+
routine: R,
194+
teardown: T,
195+
) where
196+
S: FnMut() -> I,
197+
R: FnMut(&mut I) -> RF,
198+
T: FnMut(I) -> TF,
199+
RF: Future<Output = O>,
200+
TF: Future<Output = ()>,
201+
{
202+
self.__codspeed_root_frame__iter_manual_setup_teardown(opts, setup, routine, teardown);
203+
}
204+
205+
#[inline(never)]
206+
#[allow(missing_docs, non_snake_case)]
207+
pub fn __codspeed_root_frame__iter_manual_setup_teardown<I, O, S, R, T, RF, TF>(
159208
&mut self,
160209
opts: IterManualOptions,
161210
mut setup: S,
@@ -172,8 +221,7 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> {
172221
runner.block_on(async {
173222
b.iterated = true;
174223

175-
let bench_start = InstrumentHooks::current_timestamp();
176-
let time_start = Instant::now();
224+
let mut elapsed_time = Duration::ZERO;
177225

178226
for _ in 0..opts.warmup_rounds {
179227
let mut input = black_box(setup());
@@ -186,18 +234,23 @@ impl<'a, 'b, A: AsyncExecutor, M: Measurement> AsyncBencher<'a, 'b, A, M> {
186234
let mut samples = Vec::with_capacity(opts.rounds as usize);
187235
for _ in 0..opts.rounds {
188236
let mut input = black_box(setup());
237+
238+
let bench_start = InstrumentHooks::current_timestamp();
239+
let round_start = Instant::now();
189240
let start = b.measurement.start();
190241
for _ in 0..opts.iterations {
191242
black_box(routine(&mut input).await);
192243
}
193244
let value = b.measurement.end(start);
245+
elapsed_time += round_start.elapsed();
246+
let bench_end = InstrumentHooks::current_timestamp();
247+
InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end);
248+
194249
teardown(input).await;
195250
samples.push(b.measurement.to_f64(&value));
196251
}
197252

198-
b.elapsed_time = time_start.elapsed();
199-
let bench_end = InstrumentHooks::current_timestamp();
200-
InstrumentHooks::instance().add_benchmark_timestamps(bench_start, bench_end);
253+
b.elapsed_time = elapsed_time;
201254

202255
b.codspeed_manual = Some(ManualMeasurement {
203256
samples,

0 commit comments

Comments
 (0)