Repository navigation
perf(processors): moving_windows buffer ping-pong, direct scipy.fft convolution, upsampler slice fill - #217
ggmarshall wants to merge 4 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR targets performance and correctness in the LEGEND-200 germanium DSP chain by removing hot-loop allocations, preventing unintended input mutation, and hardening centroid/extrema logic—backed by added/updated regression tests.
Changes:
- Fix correctness issues in centroid/extrema handling (e.g.,
get_wf_centroidundefined-crossing behavior;get_multi_local_extremaminima coincidence bug) and adjustwf_alignmentto treat NaN centroid as a fallback case. - Introduce
utils.contains_nan()and replace manynp.isnan(...).any()guards in numba kernels to avoid boolean-array allocations and enable early-exit scanning. - Speed up key processors (moving windows ping-pong buffering, scalar indexing in
linear_slope_*, FFT convolution path changes) and improve LH5 load error chaining.
Reviewed changes
Copilot reviewed 38 out of 38 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/processors/test_wf_alignment.py | Updates centroid/NaN alignment expectations and adds a NaN-centroid regression test. |
| tests/processors/test_get_wf_centroid.py | Adds no-zero-crossing regression coverage for centroid calculation. |
| tests/processors/test_get_multi_local_extrema.py | Adds regression test covering conservative-search minima correctness. |
| tests/processors/test_convolutions.py | Adds initial mode/NaN/mutation coverage for convolution processors. |
| tests/conftest.py | Makes session teardown robust when tmptestdir wasn’t used. |
| src/dspeed/utils.py | Adds contains_nan() numba helper for allocation-free NaN detection. |
| src/dspeed/processors/windower.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/wf_correction.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/wf_alignment.py | Uses contains_nan() and treats NaN centroid as a non-fatal fallback window. |
| src/dspeed/processors/upsampler.py | Uses contains_nan() and optimizes fill logic via clipped slice assignment. |
| src/dspeed/processors/trap_filters.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/time_point_thresh.py | Switches NaN guards to contains_nan() (including array thresholds). |
| src/dspeed/processors/time_over_threshold.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/sort.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/soft_pileup_corr.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/saturation.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/recursive_filter.py | Switches NaN guards to contains_nan() for input/coeff arrays. |
| src/dspeed/processors/rc_cr2.py | Switches NaN guards/checks to contains_nan(). |
| src/dspeed/processors/pulse_injector.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/presum.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/poly_fit.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/pole_zero.py | Switches NaN guards/checks to contains_nan(). |
| src/dspeed/processors/pmt_pulse_injector.py | Switches NaN guards to contains_nan(). |
| src/dspeed/processors/pad.py | Switches NaN guard to contains_nan() for the padded input region. |
| src/dspeed/processors/moving_windows.py | Uses contains_nan() and reduces copies via scratch-buffer ping-pong; carries running accumulator in registers. |
| src/dspeed/processors/min_max.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/log_check.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/linear_slope_fit.py | Uses contains_nan() and fixes hot-loop temporaries by indexing scalar outputs. |
| src/dspeed/processors/inl_correction.py | Switches NaN guard to contains_nan() for INL array. |
| src/dspeed/processors/histogram.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/histogram_stats.py | Switches NaN guards to contains_nan() for weights. |
| src/dspeed/processors/get_wf_centroid.py | Fixes undefined-crossing handling (avoid OOB read) and uses contains_nan(). |
| src/dspeed/processors/get_multi_local_extrema.py | Fixes conservative minima coincidence list source; uses contains_nan(). |
| src/dspeed/processors/fixed_time_pickoff.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/convolutions.py | Prevents NaN zeroing from mutating shared inputs; switches FFT implementation to scipy.fft. |
| src/dspeed/processors/bl_subtract.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processors/arithmetic.py | Switches NaN guard to contains_nan(). |
| src/dspeed/processing_chain.py | Fixes LH5 decode exception handling and chains the original exception. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #217 +/- ##
==========================================
- Coverage 69.10% 68.80% -0.30%
==========================================
Files 62 62
Lines 4191 4222 +31
==========================================
+ Hits 2896 2905 +9
- Misses 1295 1317 +22 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
a7f0a4a to
e3e21a6
Compare
|
see full report here: https://claude.ai/code/artifact/6d1c437c-c519-4797-a510-993d6aa80e98 |
|
@iguinn can we merge this? gives good speedups, all outputs have ben tested, most of this is just cleaning up the nan check |
|
Can you break this up? Almost all of the gains here are going to come from |
e4c3948 to
3f66538
Compare
|
Broken up as requested: #219 was the |
3f66538 to
3a942f7
Compare
moving_window_multi copied the full waveform 1+num_mw times per call; ping-pong between w_out and a single scratch buffer instead, choosing the start buffer so the last pass lands in w_out. Carry the running average in a register (cast back to the element dtype each step) in all three processors: numba cannot prove input/output don't alias, so indexing w_out[i-1] reloaded the accumulator from memory every sample. Bit-identical outputs; 1.3-1.4x on the germanium chain cases. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replicates scipy.signal.fftconvolve with the same next_fast_len FFT length, so outputs are bit-identical, while skipping its per-call python plumbing (shape/axis normalization, swap checks, _centered), which is significant at the 16-waveform block size the processing chain uses. ~1.15x on germanium-chain shapes; also drops one intermediate allocation via the in-place spectrum multiply. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ds check Bit-identical outputs (the NaN prefill still covers gap samples for fractional upsample factors); 1.3-1.7x on germanium-chain shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3a942f7 to
628f0a8
Compare
|
Split further: correctness fixes (with their regression tests) are now #221; this PR keeps only the perf changes, stacked on #221. Both branches rebased onto main now that #220 is merged. The union of the stack is byte-identical to the previously validated tree, and the full suite passes on each branch. |
Note
Now performance changes only, top of the stack: #219, #220 (merged) → #221 (correctness fixes) → this. Lower PRs' commits appear in this diff until they merge; I'll rebase after each.
A/B-benchmarked on real L200 p18 waveform blocks through the production
build_dsppath; outputs bit-identical.moving_windows: ping-pong one scratch buffer instead of1+num_mwcopies per call; keep the running average in a registerfft_convolve_wf: callscipy.fftdirectly (samenext_fast_lenlength), skippingfftconvolve's per-call python overheadupsampler: clipped slice fill instead of per-sample bounds checkWhole stack (all four PRs): ICPC 11.2 → 6.9 s, COAX 13.7 → 8.6 s per channel-file; verified bit-identical end-to-end against a stock-dspeed control run (report).
🤖 Generated with Claude Code