Skip to content

Commit fa9fc98

Browse files
authored
feat: add flow-control benchmark scenarios (#3596)
1 parent 9edeb52 commit fa9fc98

3 files changed

Lines changed: 96 additions & 45 deletions

File tree

neqo-http3/benches/common.rs

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@
44
// option. This file may not be copied, modified, or distributed
55
// except according to those terms.
66

7-
use std::time::Duration;
7+
#![expect(
8+
dead_code,
9+
reason = "Included by two bench binaries; each uses only one entry point."
10+
)]
811

9-
use criterion::{BenchmarkGroup, Criterion};
12+
use std::{hint::black_box, time::Duration};
13+
14+
use criterion::{Criterion, Throughput};
1015
use test_fixture::{
1116
boxed, fixture_init,
1217
sim::{
@@ -19,34 +24,105 @@ use test_fixture::{
1924
const RTT: Duration = Duration::from_millis(10);
2025

2126
/// Benchmark parameters: `(streams, data_size)`.
27+
///
28+
/// Data sizes are well below the 1 MB per-stream flow-control window, so these
29+
/// benchmarks measure raw throughput and scheduling overhead without any
30+
/// flow-control blocking.
2231
const BENCHMARK_PARAMS: [(usize, usize); 3] = [(1, 1_000), (1_000, 1), (1_000, 1_000)];
2332

24-
/// Creates a ready simulator for benchmarking HTTP/3 streams.
25-
pub fn setup(streams: usize, data_size: usize) -> ReadySimulator {
33+
/// Flow-control benchmark parameters: `(streams, data_size)`.
34+
///
35+
/// Data sizes meet or exceed the 1 MB per-stream and 2 MB connection-level flow-control
36+
/// windows, so streams regularly block waiting for `MAX_STREAM_DATA` grants.
37+
const FC_BENCHMARK_PARAMS: [(usize, usize); 2] = [
38+
(1, 4 * 1024 * 1024), // 4× per-stream window
39+
(10, 1 * 1024 * 1024), // each stream hits per-stream window; 5× connection-level window
40+
];
41+
42+
fn setup_with_link(
43+
streams: usize,
44+
data_size: usize,
45+
link: impl Fn() -> TailDrop,
46+
) -> ReadySimulator {
2647
let nodes = boxed![
2748
Node::default_client(boxed![Requests::new(streams, data_size)]),
28-
TailDrop::dsl_uplink(),
49+
link(),
2950
Delay::new(RTT),
3051
Node::default_server(boxed![Responses::new(streams, data_size)]),
31-
TailDrop::dsl_uplink(),
52+
link(),
3253
Delay::new(RTT),
3354
];
3455
Simulator::new("", nodes).setup()
3556
}
3657

37-
/// Runs benchmarks for all parameter combinations.
58+
/// Creates a ready simulator over a DSL-like link.
59+
///
60+
/// The DSL uplink (200 KB/s, 60 ms one-way delay) keeps the bandwidth-delay
61+
/// product well below the default flow-control window, so no FC blocking occurs.
62+
pub fn setup(streams: usize, data_size: usize) -> ReadySimulator {
63+
setup_with_link(streams, data_size, TailDrop::dsl_uplink)
64+
}
65+
66+
/// Creates a ready simulator over a fast link where flow-control blocking occurs.
3867
///
39-
/// The closure receives the benchmark group and parameters, allowing each
40-
/// benchmark to define its own measurement approach.
41-
pub fn benchmark<M>(c: &mut Criterion, mut measure: M)
42-
where
43-
M: FnMut(&mut BenchmarkGroup<'_, criterion::measurement::WallTime>, usize, usize),
44-
{
68+
/// At 100 MB/s with a 20 ms RTT the bandwidth-delay product (~2 MB) exceeds the
69+
/// 1 MB per-stream flow-control window, so streams regularly exhaust their window
70+
/// and block waiting for `MAX_STREAM_DATA`.
71+
pub fn setup_flow_controlled(streams: usize, data_size: usize) -> ReadySimulator {
72+
// Link delay is zero so propagation RTT comes entirely from the Delay nodes
73+
// (10 ms each way = 20 ms RTT).
74+
setup_with_link(streams, data_size, || {
75+
TailDrop::new(100_000_000, 2_000_000, false, Duration::ZERO)
76+
})
77+
}
78+
79+
type SetupFn = fn(usize, usize) -> ReadySimulator;
80+
81+
/// All benchmark configurations: `(criterion group, setup fn, params)`.
82+
const CONFIGS: [(&str, SetupFn, &[(usize, usize)]); 2] = [
83+
("streams", setup, &BENCHMARK_PARAMS),
84+
(
85+
"streams-flow-controlled",
86+
setup_flow_controlled,
87+
&FC_BENCHMARK_PARAMS,
88+
),
89+
];
90+
91+
/// Runs all stream benchmarks measuring wall-clock CPU time.
92+
pub fn walltime(c: &mut Criterion) {
4593
fixture_init();
94+
for (group_name, setup_fn, params) in CONFIGS {
95+
let mut group = c.benchmark_group(group_name);
96+
for &(streams, data_size) in params {
97+
let name = format!("walltime/{streams}-streams/each-{data_size}-bytes");
98+
group.bench_function(&name, |b| {
99+
b.iter_batched(
100+
|| setup_fn(streams, data_size),
101+
|sim| black_box(sim.run()),
102+
criterion::BatchSize::SmallInput,
103+
);
104+
});
105+
}
106+
group.finish();
107+
}
108+
}
46109

47-
let mut group = c.benchmark_group("streams");
48-
for (streams, data_size) in BENCHMARK_PARAMS {
49-
measure(&mut group, streams, data_size);
110+
/// Runs all stream benchmarks measuring simulated network time (throughput).
111+
pub fn simulated(c: &mut Criterion) {
112+
fixture_init();
113+
for (group_name, setup_fn, params) in CONFIGS {
114+
let mut group = c.benchmark_group(group_name);
115+
for &(streams, data_size) in params {
116+
let name = format!("simulated/{streams}-streams/each-{data_size}-bytes");
117+
group.throughput(Throughput::Bytes((streams * data_size) as u64));
118+
group.bench_function(&name, |b| {
119+
b.iter_custom(|iters| {
120+
(0..iters)
121+
.map(|_| setup_fn(streams, data_size).run())
122+
.sum::<Duration>()
123+
});
124+
});
125+
}
126+
group.finish();
50127
}
51-
group.finish();
52128
}

neqo-http3/benches/streams_simulated.rs

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,13 @@
1313
reason = "Inherent in codspeed criterion_group! macro."
1414
)]
1515

16-
use std::time::Duration;
17-
18-
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
16+
use criterion::{Criterion, criterion_group, criterion_main};
1917

2018
#[path = "common.rs"]
2119
mod common;
2220

2321
fn benchmark(c: &mut Criterion) {
24-
common::benchmark(c, |group, streams, data_size| {
25-
let bench_name = format!("simulated/{streams}-streams/each-{data_size}-bytes");
26-
group.throughput(Throughput::Bytes((streams * data_size) as u64));
27-
group.bench_function(&bench_name, |b| {
28-
b.iter_custom(|iters| {
29-
let mut d_sum = Duration::ZERO;
30-
for _i in 0..iters {
31-
d_sum += common::setup(streams, data_size).run();
32-
}
33-
d_sum
34-
});
35-
});
36-
});
22+
common::simulated(c);
3723
}
3824

3925
criterion_group!(benches, benchmark);

neqo-http3/benches/streams_walltime.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,13 @@
1111
reason = "Inherent in codspeed criterion_group! macro."
1212
)]
1313

14-
use std::hint::black_box;
15-
1614
use criterion::{Criterion, criterion_group, criterion_main};
1715

1816
#[path = "common.rs"]
1917
mod common;
2018

2119
fn benchmark(c: &mut Criterion) {
22-
common::benchmark(c, |group, streams, data_size| {
23-
let bench_name = format!("walltime/{streams}-streams/each-{data_size}-bytes");
24-
group.bench_function(&bench_name, |b| {
25-
b.iter_batched(
26-
|| common::setup(streams, data_size),
27-
|sim| black_box(sim.run()),
28-
criterion::BatchSize::SmallInput,
29-
);
30-
});
31-
});
20+
common::walltime(c);
3221
}
3322

3423
criterion_group!(benches, benchmark);

0 commit comments

Comments
 (0)