Adaptive spot finder: pin the per-ring host buffers

The GPU engine copies six small per-ring arrays back to the host every frame - the
clipped raw sum/sum2/count that the threshold is computed from, and the plain
corrected sum/sum2/count that become the azimuthal profile. They were plain
std::vectors, so the copies landed in pageable memory, and a device-to-host copy
into pageable memory blocks the calling thread until it has completed whatever
stream it was issued on. The profile snapshot sits between the plain pass and the
two sigma-clip passes, so Detect() stopped there and the device then sat idle while
the host caught up and enqueued the rest.

Register them, as AzIntEngineGPU already does with its own, and the copies are
genuinely asynchronous. Measured on a 4.5 Mpixel frame: 0.647 -> 0.621 ms per
frame. Nothing else changes - the spot list and the profile are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-08 15:33:24 +02:00
co-authored by Claude Opus 5
parent 5830f78d57
commit 3ccb97e31b
2 changed files with 18 additions and 0 deletions
@@ -191,6 +191,12 @@ AdaptiveSpotFinderGPU::AdaptiveSpotFinderGPU(const AzimuthalIntegrationMapping &
prof_sum(nbins),
prof_sum2(nbins),
prof_count(nbins),
host_sum_reg(host_sum),
host_sum2_reg(host_sum2),
host_count_reg(host_count),
prof_sum_reg(prof_sum),
prof_sum2_reg(prof_sum2),
prof_count_reg(prof_count),
extractor(static_cast<int32_t>(in_mapping.GetWidth()),
static_cast<int32_t>(in_mapping.GetHeight()), std::move(in_stream)),
last_profile(in_mapping) {
@@ -80,6 +80,18 @@ class AdaptiveSpotFinderGPU : public ImageSpotFinder {
std::vector<float> prof_sum2; // plain corrected sum^2 }
std::vector<uint32_t> prof_count; // plain pixel count }
// Every per-ring array above is a device-to-host copy once per frame. A D2H copy into PAGEABLE
// memory blocks the host until it completes, whatever stream it was issued on - which would stall
// Detect() between the plain pass and the clip passes, with the device then idle while the host
// enqueues them. Pinning the destinations makes the copies genuinely asynchronous, as the
// azimuthal-integration engine already does with its own.
CudaRegisteredVector<unsigned long long> host_sum_reg;
CudaRegisteredVector<unsigned long long> host_sum2_reg;
CudaRegisteredVector<uint32_t> host_count_reg;
CudaRegisteredVector<float> prof_sum_reg;
CudaRegisteredVector<float> prof_sum2_reg;
CudaRegisteredVector<uint32_t> prof_count_reg;
SpotExtractorGPU extractor; // builds the spots from gpu_strong without it leaving the device
AzimuthalIntegrationProfile last_profile; // filled every Run(), retrievable via GetProfile()