perf: the measurements pane keys its shared walks by channel and HF corner

The pane shared its two walks per *channel*, but the HF corner is a per-measurement
setting: two measurements on one signal at different corners read two different signals
and cannot share a walk. `App::MeasureWindow` therefore carries `channel` and `fc`, the
container is a vector, and the grouping loop matches on both -- keeping its dedupe of the
level accumulators by `(pct, noise)`, since a type that does not read the band must not
split an accumulator over it.

Each window carries two filters, one per walk: they are separate walks, so separate state.
A filtered window also stops using the LOD pyramid, which one `if` does -- leaving
`buckets` empty makes `bulk_from == bulk_to == n`, so the raw walk covers the window and
`lo_search`/`hi_search` stay 0, which makes the pinning pass a no-op. Nothing downstream
needs to know.

The readout loop looks its window up by index rather than by channel, which is what the
`(channel, fc)` key needs.

The documented measurement baseline is unchanged, and it is the assertion:

    [measure-trace] average=0.000000 top=0.636620 peak to peak=2.000000 amplitude=1.000000
    [udpscope] measure cost: ... covered=75378013 buckets=1177164 raw=159674 (0.2%)

`top` is 2/pi to the digit -- the one reading a band leaking into the sums would move --
and `raw` at 0.2 % of `covered` says the fast path is still being used on an unfiltered
window.

The measure probe now enables `sine` itself, as the noise probe does: `enable_all_probe`
ends by switching a signal off, and with `sine` gone the probe measured `square`, where
`top` is 1.0 and the baseline above is unreachable.
This commit is contained in:
Martino Ferrari committed 2026-10-13 15:38:53 +02:00
1 parent 2730707372
commit 380ddb9a77
3 files changed
+79 -17

No files matched your search

+12 -2
View File
@@ -274,13 +274,23 @@ struct App {
/// them asked for. Several measurements on one signal must cost two walks, not two each -- and
/// the window itself is never stored, which is what the chunked walks bought (see
/// `MeasureChunk`).
/// One channel's window, at one HF corner, with every level accumulator the pane asked for.
///
/// Keyed by `(channel, fc)` and not by channel alone: the corner is a per-measurement setting, so two
/// measurements on one signal at different corners are reading two different signals and cannot share a
/// walk.
struct MeasureWindow {
int channel = -1;
double fc = 0.0; ///< the HF corner this window was walked at; 0 is unfiltered
MeasureStats stats;
std::vector<double> pct; ///< the level shares asked for, deduplicated
std::vector<double> pct; ///< the level shares asked for, deduplicated
std::vector<double> noise; ///< each entry's band, as a percentage; parallel to `pct`
std::vector<MeasureLevel> level; ///< one accumulator per entry of `pct`
MeasureFilter scan_filter; ///< pass one's state
MeasureFilter acc_filter; ///< pass two's; a separate walk, so a separate state
bool ok = false;
};
std::unordered_map<int, MeasureWindow> measure_samples;
std::vector<MeasureWindow> measure_samples;
/// The window's LOD buckets, reused across recomputes: a 1.7 M-sample window is 26 k of them,
/// and both passes walk the same list.
std::vector<MeasureBucket> measure_bucket_scratch;