From c10d7ced172baadbd6f1f863341193118972fb6b Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Wed, 29 Jul 2026 09:38:11 +0200 Subject: [PATCH] rugnux: build the first-pass analysis engine once, not per image The first-pass spot cache constructed an MXAnalysisWithoutFPGA and an AzimuthalIntegrationProfile inside the per-image lambda, so every cache miss allocated a CUDA stream, the preprocessing buffer, the spot finder, the azimuthal integrator and the Bragg engine, used them for one frame, and freed them again - hundreds of times, serially, on the --redo-rotation-spots path. Both worker loops already hoist the same object out of their loop; only this path did not. Build them once for the whole first pass, and only when spots actually have to be found (with --reuse-rotation-spots there is nothing to allocate). Reuse is safe because every azimuthal-integration path - CPU, GPU and the fused adaptive engine - clears the caller's profile before adding to it, so each frame's output is unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- rugnux/Rugnux.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rugnux/Rugnux.cpp b/rugnux/Rugnux.cpp index 205ca1ea..3a6b46ea 100644 --- a/rugnux/Rugnux.cpp +++ b/rugnux/Rugnux.cpp @@ -506,6 +506,18 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b // validation set; re-finding is expensive on the --redo-rotation-spots path). Accessed only // from single-threaded sections (the scheme feed and the validation loop), so no locking. std::map> spot_cache; + // One analysis engine for the whole first pass. Constructing it allocates a CUDA stream and a + // full set of GPU buffers (preprocessing, spot finding, azimuthal integration, Bragg + // integration) - far more work than analysing a frame - so building it per image made + // --redo-rotation-spots pay for hundreds of them, serially. Only needed when spots have to be + // found; this section is single-threaded, so one engine is enough. + std::unique_ptr analysis; + std::unique_ptr profile; + if (!reuse_spots) { + analysis = std::make_unique(experiment_, mapping, pixel_mask_, *indexer, + /*enable_fused_adaptive_gpu=*/true); + profile = std::make_unique(mapping); + } auto get_spots = [&](int ordinal) -> const std::vector & { auto it = spot_cache.find(ordinal); if (it != spot_cache.end()) @@ -519,16 +531,13 @@ ProcessResult Rugnux::RunPipeline(RugnuxObserver *observer, bool write_output, b DataMessage m{}; m.number = ordinal; m.original_number = image_idx; - MXAnalysisWithoutFPGA analysis(experiment_, mapping, pixel_mask_, *indexer, - /*enable_fused_adaptive_gpu=*/true); - AzimuthalIntegrationProfile profile(mapping); auto first_pass = config_.spot_finding; first_pass.indexing = false; first_pass.quick_integration = false; m.image = img->image; if (dataset->efficiency.size() > image_idx) m.image_collection_efficiency = dataset->efficiency[image_idx]; - analysis.Analyze(m, profile, first_pass); + analysis->Analyze(m, *profile, first_pass); spots = std::move(m.spots); } } catch (const std::exception &e) {