Files
Jungfraujoch/image_analysis/image_preprocessing/ImagePreprocessorBuffer.h
T
leonarski_fandClaude Opus 5 e4d70f0e55
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
image_preprocessing: inline the buffer accessors
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>
2026-08-02 11:20:31 +02:00

39 lines
1.7 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:
std::vector<int32_t> buffer;
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 buffer.size(); }
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;
};