image_preprocessing: inline the buffer accessors
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m6s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m15s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m41s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 13m53s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m15s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m44s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 13m6s
Build Packages / build:rpm (rocky8) (push) Successful in 12m1s
Build Packages / XDS test (durin plugin) (push) Successful in 6m58s
Build Packages / Generate python client (push) Successful in 35s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m41s
Build Packages / build:rpm (rocky9) (push) Successful in 14m0s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m59s
Build Packages / DIALS test (push) Successful in 13m49s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m38s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m20s
Build Packages / Unit tests (push) Successful in 1h1m46s
Build Packages / build:windows:nocuda (push) Failing after 2s
Build Packages / build:windows:cuda (push) Failing after 2s

operator[], size(), data() and getBuffer() are one-line accessors that were defined
in the .cpp. The build sets no link-time optimisation, so out of line each of them is
a real call - once per pixel, from the CPU preprocessor, the CPU azimuthal integrator
and the CPU spot finder - and they stop those loops vectorising at all. They show up
in a profile directly: about six per cent of a whole azimuthal-integration-only run
is spent in the call overhead of two accessors that do nothing but index a vector.

Moving them into the header retires 30% fewer instructions on that run and takes the
per-image CPU cost on a GPU-less pass from 34.6 to 24.2 ms, with the output bit for
bit unchanged - same observation count, same cell, same merge statistics. It is worth
nothing on the GPU path, where the image stays on the device, and everything on the
paths that have no GPU to fall back on.

This also explains a measurement that had been blamed on the pixel mask being a
vector<bool>: a microbenchmark of that loop indexed a raw pointer and came out far
faster than the same loop in the binary, and the difference was this call, not the
mask. Measured properly the mask costs about 14% single-threaded rather than the 41%
claimed, and at the thread counts this actually runs at the bit mask is FASTER than
the byte mask it was proposed to become, because it moves eight times less traffic
and the loop is bandwidth bound. That change should not be made.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-02 11:20:31 +02:00
co-authored by Claude Opus 5
parent 639fbb3fbc
commit e4d70f0e55
2 changed files with 12 additions and 37 deletions
@@ -5,35 +5,6 @@
ImagePreprocessorBuffer::ImagePreprocessorBuffer(size_t npixels) : buffer(npixels) {}
// Standard CPU operation
std::vector<int32_t> &ImagePreprocessorBuffer::getBuffer() {
return buffer;
}
const std::vector<int32_t> &ImagePreprocessorBuffer::getBuffer() const {
return buffer;
}
int32_t &ImagePreprocessorBuffer::operator[](size_t i) {
return buffer[i];
}
const int32_t &ImagePreprocessorBuffer::operator[](size_t i) const {
return buffer[i];
}
size_t ImagePreprocessorBuffer::size() const {
return buffer.size();
}
int32_t *ImagePreprocessorBuffer::data() {
return buffer.data();
}
const int32_t *ImagePreprocessorBuffer::data() const {
return buffer.data();
}
void ImagePreprocessorBuffer::Gather(const std::vector<uint32_t> &npixel, std::vector<int32_t> &values) const {
values.resize(npixel.size());
for (size_t i = 0; i < npixel.size(); i++)