Skip to main content

perf_construction/
perf_construction.rs

1//! Construction-time latency benchmark for `into_handler`, `into_callback`,
2//! and `into_stage`.
3//!
4//! Measures the cold-path cost of resolving SystemParam state + access
5//! conflict detection at various arities. This is paid once per handler
6//! at build time — never on the dispatch hot path.
7//!
8//! Run with:
9//! ```bash
10//! taskset -c 0 cargo run --release -p nexus-rt --example perf_construction
11//! ```
12
13use std::hint::black_box;
14
15use nexus_rt::{IntoCallback, IntoHandler, Local, PipelineStart, Res, ResMut, WorldBuilder};
16
17// =============================================================================
18// Bench infrastructure (same as perf_pipeline.rs)
19// =============================================================================
20
21const ITERATIONS: usize = 100_000;
22const WARMUP: usize = 10_000;
23const BATCH: u64 = 100;
24
25#[inline(always)]
26#[cfg(target_arch = "x86_64")]
27fn rdtsc_start() -> u64 {
28    unsafe {
29        core::arch::x86_64::_mm_lfence();
30        core::arch::x86_64::_rdtsc()
31    }
32}
33
34#[inline(always)]
35#[cfg(target_arch = "x86_64")]
36fn rdtsc_end() -> u64 {
37    unsafe {
38        let mut aux = 0u32;
39        let tsc = core::arch::x86_64::__rdtscp(&raw mut aux);
40        core::arch::x86_64::_mm_lfence();
41        tsc
42    }
43}
44
45fn percentile(sorted: &[u64], p: f64) -> u64 {
46    let idx = ((sorted.len() as f64) * p / 100.0) as usize;
47    sorted[idx.min(sorted.len() - 1)]
48}
49
50fn bench_batched<F: FnMut() -> u64>(name: &str, mut f: F) -> (u64, u64, u64) {
51    for _ in 0..WARMUP {
52        black_box(f());
53    }
54    let mut samples = Vec::with_capacity(ITERATIONS);
55    for _ in 0..ITERATIONS {
56        let start = rdtsc_start();
57        for _ in 0..BATCH {
58            black_box(f());
59        }
60        let end = rdtsc_end();
61        samples.push(end.wrapping_sub(start) / BATCH);
62    }
63    samples.sort_unstable();
64    let p50 = percentile(&samples, 50.0);
65    let p99 = percentile(&samples, 99.0);
66    let p999 = percentile(&samples, 99.9);
67    println!("{:<50} {:>8} {:>8} {:>8}", name, p50, p99, p999);
68    (p50, p99, p999)
69}
70
71fn print_header(title: &str) {
72    println!("=== {} ===\n", title);
73    println!(
74        "{:<50} {:>8} {:>8} {:>8}",
75        "Operation", "p50", "p99", "p999"
76    );
77    println!("{}", "-".repeat(78));
78}
79
80// =============================================================================
81// Handler functions at various arities
82// =============================================================================
83
84fn sys_1p(_a: Res<u64>, _e: ()) {}
85fn sys_2p(_a: Res<u64>, _b: ResMut<u32>, _e: ()) {}
86fn sys_4p(_a: Res<u64>, _b: ResMut<u32>, _c: Res<bool>, _d: Res<f64>, _e: ()) {}
87
88#[allow(clippy::too_many_arguments)]
89fn sys_8p(
90    _a: Res<u64>,
91    _b: ResMut<u32>,
92    _c: Res<bool>,
93    _d: Res<f64>,
94    _e2: Res<i64>,
95    _f: Res<i32>,
96    _g: Res<u8>,
97    _h: ResMut<u16>,
98    _e: (),
99) {
100}
101
102// With Local (no World resource — skipped by check_access)
103fn sys_local(_a: Local<u64>, _b: ResMut<u32>, _e: ()) {}
104
105// With Option (try_id path in init)
106fn sys_option(_a: Option<Res<u64>>, _b: ResMut<u32>, _e: ()) {}
107
108// =============================================================================
109// Callback functions
110// =============================================================================
111
112fn cb_2p(_ctx: &mut u64, _a: Res<u64>, _b: ResMut<u32>, _e: ()) {}
113fn cb_4p(_ctx: &mut u64, _a: Res<u64>, _b: ResMut<u32>, _c: Res<bool>, _d: Res<f64>, _e: ()) {}
114
115// =============================================================================
116// Stage functions
117// =============================================================================
118
119fn stage_2p(_a: Res<u64>, _b: ResMut<u32>, _x: u32) -> u32 {
120    0
121}
122fn stage_4p(_a: Res<u64>, _b: ResMut<u32>, _c: Res<bool>, _d: Res<f64>, _x: u32) -> u32 {
123    0
124}
125
126// =============================================================================
127// Main
128// =============================================================================
129
130fn main() {
131    // Register enough types to exercise the check_access bitset.
132    let mut wb = WorldBuilder::new();
133    wb.register::<u64>(0);
134    wb.register::<u32>(0);
135    wb.register::<bool>(false);
136    wb.register::<f64>(0.0);
137    wb.register::<i64>(0);
138    wb.register::<i32>(0);
139    wb.register::<u8>(0);
140    wb.register::<u16>(0);
141    let mut world = wb.build();
142    let r = world.registry_mut();
143
144    print_header("into_handler Construction (cycles)");
145
146    bench_batched("into_handler  1-param (Res<u64>)", || {
147        let _ = black_box(sys_1p.into_handler(r));
148        0
149    });
150
151    bench_batched("into_handler  2-param (Res + ResMut)", || {
152        let _ = black_box(sys_2p.into_handler(r));
153        0
154    });
155
156    bench_batched("into_handler  4-param", || {
157        let _ = black_box(sys_4p.into_handler(r));
158        0
159    });
160
161    bench_batched("into_handler  8-param", || {
162        let _ = black_box(sys_8p.into_handler(r));
163        0
164    });
165
166    bench_batched("into_handler  2-param (Local + ResMut)", || {
167        let _ = black_box(sys_local.into_handler(r));
168        0
169    });
170
171    bench_batched("into_handler  2-param (Option<Res> + ResMut)", || {
172        let _ = black_box(sys_option.into_handler(r));
173        0
174    });
175
176    println!();
177    print_header("into_callback Construction (cycles)");
178
179    bench_batched("into_callback 2-param (Res + ResMut)", || {
180        let _ = black_box(cb_2p.into_callback(0u64, r));
181        0
182    });
183
184    bench_batched("into_callback 4-param", || {
185        let _ = black_box(cb_4p.into_callback(0u64, r));
186        0
187    });
188
189    println!();
190    print_header("into_stage Construction (cycles)");
191
192    bench_batched(".stage() 2-param (Res + ResMut)", || {
193        let _ = black_box(PipelineStart::<u32>::new().stage(stage_2p, r));
194        0
195    });
196
197    bench_batched(".stage() 4-param", || {
198        let _ = black_box(PipelineStart::<u32>::new().stage(stage_4p, r));
199        0
200    });
201
202    println!();
203}