Three resource fixes and two latent bugs, none of which changes a computed number. The preprocessed image has a host copy that only a CPU engine ever reads. On the GPU path every engine reads the device buffer instead, and rugnux always runs the fused adaptive finder, so that host copy is allocated, zeroed and PAGE-LOCKED for nothing - 72 MB per worker, 3.5 GB over 48 of them, and a cudaHostRegister each, which the driver serializes. It is now skipped by the same condition that already decides whether the device copies the image back. ImagePreprocessorBuffer keeps the pixel count separately so size() still answers when the mirror was not allocated. ROIIntegrationGPU asked device 0 for the SM count it sizes its grid from, while workers are pinned round-robin across the GPUs - so on a multi-GPU node it could size a grid from a card it never launches on. It asks the current device now, like every other engine. ~CudaRegisteredVector called a function that throws out of a destructor, and the move-assignment did the same from a noexcept function. Either would abort the process rather than report the failure, and teardown - after a device reset, or while another exception unwinds - is exactly where cudaHostUnregister fails. Both now use an unchecked unregister, as every other destructor in that header already does for its own teardown call. The throwing form stays for rebind()/unregister(), which are called from live code. Measured on a 16M-pixel rotation dataset: unchanged space group, merged reflection count and merging statistics. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
47 lines
2.2 KiB
C++
47 lines
2.2 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
|
|
class ImagePreprocessorBuffer {
|
|
protected:
|
|
// The image size, kept apart from `buffer` so size() still answers when the host copy was not
|
|
// allocated at all (see the host_mirror constructor).
|
|
const size_t npixels;
|
|
std::vector<int32_t> buffer;
|
|
|
|
// host_mirror = false leaves `buffer` empty. On the GPU path the preprocessed image lives on the
|
|
// device and no CPU engine reads it back, so the four-bytes-per-pixel host copy would be
|
|
// allocated, zeroed and page-locked for nothing - 72 MB per worker on a 16M-pixel detector.
|
|
ImagePreprocessorBuffer(size_t npixels, bool host_mirror);
|
|
public:
|
|
explicit ImagePreprocessorBuffer(size_t npixels);
|
|
virtual ~ImagePreprocessorBuffer() = default;
|
|
|
|
// Standard CPU operation. Defined here rather than in the .cpp: these are called once per pixel by
|
|
// the CPU preprocessor, the CPU azimuthal integrator and the CPU spot finder, and the build has no
|
|
// link-time optimisation - so out of line they are a real call per pixel, and they stop the callers
|
|
// vectorising at all. Inlining them costs about a third of the per-image CPU work on every path
|
|
// that does not have a GPU, and leaves the results bit-identical.
|
|
std::vector<int32_t> &getBuffer() { return buffer; }
|
|
const std::vector<int32_t> &getBuffer() const { return buffer; }
|
|
int32_t &operator[](size_t i) { return buffer[i]; }
|
|
const int32_t &operator[](size_t i) const { return buffer[i]; }
|
|
size_t size() const { return npixels; }
|
|
|
|
int32_t *data() { return buffer.data(); }
|
|
const int32_t *data() const { return buffer.data(); }
|
|
|
|
// GPU operations (overriden in ImagePreprocessorBufferGPU
|
|
virtual int32_t *getGPUBuffer();
|
|
virtual const int32_t *getGPUBuffer() const;
|
|
|
|
// Values of the pixels with the given flat indices. Not just operator[] in a loop: on the GPU the
|
|
// preprocessed image can live in device memory only, and then this gathers them there.
|
|
virtual void Gather(const std::vector<uint32_t> &npixel, std::vector<int32_t> &values) const;
|
|
};
|