manifold_rust/timing.rs
1// Lightweight, env-gated phase timing for the boolean pipeline, mirroring the
2// C++ MANIFOLD_TIMING instrumentation (Timer::Print in boolean3.cpp /
3// boolean_result.cpp). Enabled by setting the MANIFOLD_TIMING environment
4// variable to any non-empty value; otherwise every call is a no-op so release
5// performance is unaffected. Used to compare per-stage wall-clock against the
6// C++ reference when hunting performance gaps (see CLAUDE.md "Instrumentation
7// Strategy").
8
9use std::sync::OnceLock;
10use std::time::Instant;
11
12fn enabled() -> bool {
13 static ENABLED: OnceLock<bool> = OnceLock::new();
14 *ENABLED.get_or_init(|| {
15 std::env::var("MANIFOLD_TIMING").map_or(false, |v| !v.is_empty())
16 })
17}
18
19/// Start a stage timer. Returns None (and times nothing) unless the
20/// MANIFOLD_TIMING environment variable is set.
21pub(crate) fn start() -> Option<Instant> {
22 if enabled() {
23 Some(Instant::now())
24 } else {
25 None
26 }
27}
28
29/// Optional memory reporter, registered by profiling harnesses (e.g. the
30/// mem_profile example's counting allocator). Returns (current bytes, peak
31/// bytes since the previous call) — the implementation resets its peak
32/// watermark on read so each stage line reports that stage's own peak.
33pub type MemHook = fn() -> (usize, usize);
34
35static MEM_HOOK: OnceLock<MemHook> = OnceLock::new();
36
37pub fn set_mem_hook(hook: MemHook) {
38 let _ = MEM_HOOK.set(hook);
39}
40
41/// Print the elapsed time for a stage started with `start`, matching the C++
42/// Timer::Print format ("label: N sec") on stderr. If a memory hook is
43/// registered, appends current/stage-peak heap use.
44pub(crate) fn print(label: &str, t0: Option<Instant>) {
45 if let Some(t0) = t0 {
46 match MEM_HOOK.get() {
47 Some(hook) => {
48 let (current, peak) = hook();
49 eprintln!(
50 "{}: {} sec, current = {:.1} MB, stage peak = {:.1} MB",
51 label,
52 t0.elapsed().as_secs_f64(),
53 current as f64 / 1048576.0,
54 peak as f64 / 1048576.0
55 );
56 }
57 None => eprintln!("{}: {} sec", label, t0.elapsed().as_secs_f64()),
58 }
59 }
60}