Skip to main content

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}