Repository navigation
Add NDArray.read_into(): decompress a selection into an existing array - #762
Merged
Merged
Conversation
FabianIsensee
force-pushed
the
read-into
branch
from
November 5, 2026 11:20
fbcbf09 to
dd2e1d9
Compare
arr[key] always returns a new array, so code that needs the data in an existing array (the interior of a padded patch, one item of a batch, a pinned or reused buffer) pays for a temporary, its page faults and one more copy. read_into(out, key) decompresses arr[key] straight into `out`, a NumPy array of the selection's shape. For basic keys (integers, slices with step 1, Ellipsis) and `out` of the array's dtype whose last axis is contiguous and whose other strides are whole multiples of the next one, i.e. any C-contiguous array or box of one, also with steps in the outer axes, the data goes straight to `out` through b2nd_get_slice_cbuffer_into() and nothing outside `out` is touched; the GIL is released meanwhile as in the other slice reads. Any other layout, key or dtype (`casting` as in numpy.copyto) goes through arr[key], so every writeable NumPy array works. As in the fast path of __getitem__, basic keys are resolved in Cython against the cached shape, and the read is retried if it finds that another handle changed the shape (SWMR), so small reads cost no more per call than arr[key]. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01SwMZj7wsgYdoCR5uvYPwEo
FabianIsensee
force-pushed
the
read-into
branch
from
November 5, 2026 11:32
dd2e1d9 to
18df65d
Compare
| set(BLOSC2_BUNDLED_VERSION v3.5.0) | ||
| # TEMPORARY: build against the companion c-blosc2 PR branch until it is merged and released | ||
| set(BLOSC2_BUNDLED_REPOSITORY https://github.com/FabianIsensee/c-blosc2) | ||
| set(BLOSC2_BUNDLED_VERSION 64a3aa6f9a54082ba7df0d79291953610aa2e482) |
Member
There was a problem hiding this comment.
This has been merged already in c-blosc2 repo. Feel free to update this at your convenience.
Contributor
Author
There was a problem hiding this comment.
Thanks! Done: the pin now points to c-blosc2 main e3ff64f2 (#844, #845 and the short-rows copy), and BLOSC2_MIN_VERSION is 3.5.1, because a system c-blosc2 3.5.0 lacks b2nd_get_slice_cbuffer_into() (main reports 3.5.1.dev, which pkg-config accepts). Once the next c-blosc2 release is out, the pin can become its tag.
read_into() needs b2nd_get_slice_cbuffer_into() (c-blosc2 #845), which is on c-blosc2 main but not in a release yet. Pin the bundled c-blosc2 to the current main commit until the next release, and require at least 3.5.1 when building against a system c-blosc2 (3.5.0 lacks the function; main reports 3.5.1.dev, which pkg-config accepts). Co-Authored-By: Claude Opus 5.5 <[email protected]>
FabianIsensee
force-pushed
the
read-into
branch
from
November 5, 2026 12:17
18df65d to
8702873
Compare
Member
|
LGTM. Thanks @FabianIsensee ! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hi Francesc,
Depends on:
b2nd_get_slice_cbuffer_into()from the c-blosc2 PRs Blosc/c-blosc2#844 and Blosc/c-blosc2#845, both merged. The last commit bundles c-blosc2 main e3ff64f2 until the next c-blosc2 release and raisesBLOSC2_MIN_VERSIONto 3.5.1, since a system c-blosc2 3.5.0 lacks the function. Rebased onto main after #743 and the other merges of today.arr[key]always returns a new array. Code that needs the data in an existing array pays for a temporary, its page faults and one more copy: the interior of a padded patch (CNN training on images smaller than the patch), one item of a batch, a reused or pinned buffer for GPU transfers. NumPy hasout=, h5py hasDataset.read_direct(), zarr hasget_basic_selection(..., out=); python-blosc2 only has the undocumentedget_slice_numpy(), which needs a C-contiguous buffer of exactly the slice's shape.NDArray.read_into(out, key=None, casting="same_kind")decompressesarr[key]into the NumPy arrayout:outof the array's dtype, andoutwhose last axis is contiguous and whose other strides are whole multiples of the next one, the data goes straight intooutthroughb2nd_get_slice_cbuffer_into(). That covers C-contiguous arrays and every box of one, also with steps in the outer axes. Nothing outsideoutis touched. The GIL is released meanwhile, as in the other slice reads.castingas innumpy.copyto) goes througharr[key]and one copy, so any writeable NumPy array works.outmust have the selection's shape (no broadcasting). It returnsout.It is documented in the NDArray reference next to
__getitem__, with examples.Benchmarks
Ryzen 9 7950X, warm, on disk, median of 15: a (4, 132, 162, 133) float32 case into the interior of a (4, 160, 192, 160) patch (fresh per call), against
patch[interior] = a[...]: 16.1 -> 12.2 ms with 1 thread, 16.1 -> 11.8 ms with 8. Into one item of a batch or into a reused buffer it is not faster thanbatch[j] = a[key](0.85-1.0x with 1 thread, 1.0x with 8): there the temporary is cheap, as malloc reuses it and it stays in cache; the gain is in avoiding it where it is not. Per call it costs no more thana[key]: an 8x8x8 crop into a reused buffer takes 12.4 us, against 13.0 us forbuf[...] = a[key]. With nnU-Net's data loader on BraTS (cases smaller than the patch), a prototype cut the time per sample by 22% and its CPU time by 23%.Tests
tests/ndarray/test_read_into.py: in memory, contiguous and sparse frames on disk, and mmap; six dtypes and eleven keys into contiguousout(checked to bypass__getitem__); interiors of padded arrays, batch items, channels and strided views with nothing outsideoutchanged; Fortran-order, transposed, last-axis-step and negative-strideout; other keys (steps, index arrays also as blosc2 arrays, masks, fields) and dtypes,casting; empty and 0-d selections; errors; shape changes made by another handle (SWMR), also with anoutof the old shape; threads with one handle each; and 240 random keys into random views of larger arrays against NumPy. Full suite after the rebase: 10634 passed; the 2 b2view HTTP tests fail without aiohttp, as on main.AI disclosure: I used Claude Code (Anthropic) for the code, the tests and a first draft of this description, and Claude Code opened this PR on my behalf. I reviewed and tested everything.
Best,
Fabian
🤖 Generated with Claude Code