Files
ruvnet--RuView/v2/crates/wifi-densepose-vitals/benches/vitals_bench.rs
T
ruv a7f7adfabc perf(vitals,wifiscan): O(1) VecDeque sliding windows + vitals bench (ADR-157 §A1/§D1)
Replace Vec::remove(0) (O(n) per-sample buffer shift -> O(n^2) full-window
sweep) with VecDeque push_back/pop_front (O(1) eviction) in the fixed-length
sliding/ring buffers of the vital-sign and wifiscan extractors. Where the
autocorrelation / zero-crossing / Pearson loop needs a contiguous slice,
make_contiguous() is called once per extract(), matching the idiom already used
in wifiscan/pipeline/orchestrator.rs. Output is bit-identical.

Sites: anomaly.rs (rr/hr history), store.rs (readings ring; history() now takes
&mut self to hand back a contiguous slice, no external callers), wifiscan
breathing_extractor.rs (filtered history), wifiscan correlator.rs (per-BSSID
histories -> Vec<VecDeque<f32>>). (heartrate.rs/breathing.rs windows land with
the §A2/§A3 fixes in a separate commit.)

New criterion bench crates/wifi-densepose-vitals/benches/vitals_bench.rs drives
each extractor over a full-window fill. Honest MEASURED result: end-to-end win
is NULL within noise at realistic ESP32 window sizes (1500-3000) because the
per-frame DSP dominates the eviction (heartrate 42.8ms->44.4ms, breathing
7.95ms->7.86ms, overlapping CIs). In isolation the eviction collapses O(n^2)
-> O(n) (34.6x at window=3000, 3158x at window=100000); A1 lands as the correct
data structure removing a latent O(n^2), NOT a claimed hot-path speedup.

Reproduce: cargo bench -p wifi-densepose-vitals --bench vitals_bench

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-06-11 20:59:57 -04:00

76 lines
3.3 KiB
Rust

//! Benchmarks for the vital-sign extractor hot paths (ADR-157 §D1).
//!
//! The extractors maintain a fixed-length sliding window of filtered samples.
//! The window was previously a `Vec<f64>` whose oldest-sample eviction used
//! `Vec::remove(0)` — an O(n) shift of the whole buffer on every sample, making
//! a full-window `extract()` sweep O(n²). ADR-157 §A1 switched the window to a
//! `VecDeque<f64>` (O(1) `push_back` + `pop_front`, with one `make_contiguous`
//! per call for the autocorrelation / zero-crossing loop).
//!
//! These benches measure the payoff: a full-window fill of each extractor
//! (`HeartRateExtractor` ~1500 samples, `BreathingExtractor` ~3000 samples).
//! Each iteration drives the extractor from empty to a full window so the
//! per-sample eviction cost (the thing A1 changed) is exercised across the
//! entire buffer.
//!
//! Reproduce:
//! cargo bench -p wifi-densepose-vitals --bench vitals_bench
//! Compile-only:
//! cargo bench -p wifi-densepose-vitals --bench vitals_bench --no-run
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use wifi_densepose_vitals::{BreathingExtractor, HeartRateExtractor};
/// Drive a heart-rate extractor from empty to a full window.
///
/// `fs = 100 Hz`, `window = 15 s` -> 1500 samples. A few coherent subcarriers
/// of a synthetic cardiac sinusoid are fed each frame; the point of the bench
/// is the sliding-window bookkeeping, not the signal content.
fn bench_heartrate_full_window(c: &mut Criterion) {
let sample_rate = 100.0;
let window_secs = 15.0;
let n_frames = (sample_rate * window_secs) as usize; // 1500
let heart_freq = 1.2; // 72 BPM
c.bench_function("heartrate_extract_full_window_1500", |b| {
b.iter(|| {
let mut ext = HeartRateExtractor::new(4, sample_rate, window_secs);
for i in 0..n_frames {
let t = i as f64 / sample_rate;
let base = (2.0 * std::f64::consts::PI * heart_freq * t).sin();
let residuals = [base * 0.1, base * 0.08, base * 0.12, base * 0.09];
let phases = [0.0, 0.01, 0.02, 0.03];
black_box(ext.extract(black_box(&residuals), black_box(&phases)));
}
black_box(ext.history_len());
});
});
}
/// Drive a breathing extractor from empty to a full window.
///
/// `fs = 100 Hz`, `window = 30 s` -> 3000 samples.
fn bench_breathing_full_window(c: &mut Criterion) {
let sample_rate = 100.0;
let window_secs = 30.0;
let n_frames = (sample_rate * window_secs) as usize; // 3000
let breathing_freq = 0.25; // 15 BPM
c.bench_function("breathing_extract_full_window_3000", |b| {
b.iter(|| {
let mut ext = BreathingExtractor::new(4, sample_rate, window_secs);
for i in 0..n_frames {
let t = i as f64 / sample_rate;
let s = (2.0 * std::f64::consts::PI * breathing_freq * t).sin();
let residuals = [s, s * 0.9, s * 1.1, s * 0.95];
let weights = [0.25, 0.25, 0.25, 0.25];
black_box(ext.extract(black_box(&residuals), black_box(&weights)));
}
black_box(ext.history_len());
});
});
}
criterion_group!(benches, bench_heartrate_full_window, bench_breathing_full_window);
criterion_main!(benches);