Two things every worker thread of an offline run did inside the global HDF5
mutex, per image.
It opened /entry/data/data and asked it for its dataspace, its datatype and its
creation plist, then asked those for the rank, the dimensions, the chunking and
the compression. All of that is a property of the file and identical for all of
its images, so it is now resolved once when the file is first touched.
And it read the pixels - megabytes of them, with the lock held, which is what
turned a worker per hardware thread into a queue. HDF5 can say where a chunk
lives instead - address and byte count, a lookup in the chunk index with no read
attached - so that is all it is asked for now, and the bytes are fetched after
the lock is dropped, with a positional read that any number of threads can make
through one handle at once. Chunk addresses count from the end of the user
block, so its size is added; zero for anything this project writes, not for
every file. A file that is not one chunk per image, or a chunk that was never
written and exists only as a fill value, still goes the old way - only HDF5
knows what those read as.
On a 16 Mpx rotation dataset with the process file being written, the per-image
loop at 48 workers goes 12.4 s -> 6.8 s, and stops getting slower as workers are
added: 8 workers were faster than 48 before, and are not now. Where no process
file is written the same loop only improves ~1%, because this machine has 1.5 TB
of RAM and held the whole 7 GB test set in page cache - the read was never the
expensive part here. It is where the cache is cold or the filesystem is remote.
Battery 9m45s, space group 21/24, no failures, unchanged.
The Windows path uses ReadFile with an OVERLAPPED offset for the same reason
pread is used elsewhere: it takes the offset as an argument rather than moving a
shared file position, so the viewer keeps building under MSVC and gets the same
concurrency.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>