Repository navigation
Description
The zero check in the encoding path is less than ideal
zarr-python/src/zarr/core/codec_pipeline.py
Line 416 in b873691
| if not chunk_spec.config.write_empty_chunks and chunk_array.all_equal( |
For lazy arrays, it eventually calls:
- np.broadcast_arrays
- np.array_equal
where the latter can instantiate a full array in memory.
The problem is that some lazy array implementation will not "keep this instantiation of the interediate full memory array" in memory for the the operation 2 lines down, that will eventually write the array.
So if the instantiation of the lazy array is costly, we are:
- Instantiating it once for checking for zeros.
- Instantiating a second time to write it to the store.
I would love to avoid this 'double cost' in a "pythonic way".
I would have loved to suggest
My proposal would be to refactor the "zero check" to use np.array_equiv https://numpy.org/devdocs/reference/generated/numpy.array_equiv.html#numpy.array_equiv
>>> import numpy as np
>>> np.array_equiv(np.zeros((3, 4)), 0)
Truebut looking at the source, it seems to recreate the same problem https://github.com/numpy/numpy/blob/main/numpy/_core/numeric.py#L2552-L2598
I'm not sure what the right solution is.
One could cache the local array.
I notice that this operation is actually considered inefficient by the original writers leaving a note like
# use array_equal to obtain equal_nan=True functionality
# Since fill-value is a scalar, isn't there a faster path than allocating a new array for fill value
# every single time we have to write data?
Where this is a problem for me:
The numpy broadcast operation is actually really difficult to implement if you want to ensure high performance based on the chunks of the array. My experience with dask from 7 years ago reminds me that it was also difficult for them to implement efficient rechunking.
So implementing the np.broadcast_arrays just feels like "alot of work"....
zarr-python/src/zarr/core/buffer/core.py
Line 543 in b873691
| _data, other = np.broadcast_arrays(self._data, other) |
The zero check feels like it makes sense.... but its like "alot of work to do" for some array backends.
Suggested patch:
diff --git a/src/zarr/core/buffer/core.py b/src/zarr/core/buffer/core.py
index f0d01566..54a2c09f 100644
--- a/src/zarr/core/buffer/core.py
+++ b/src/zarr/core/buffer/core.py
@@ -540,6 +540,11 @@ class NDBuffer:
# use array_equal to obtain equal_nan=True functionality
# Since fill-value is a scalar, isn't there a faster path than allocating a new array for fill value
# every single time we have to write data?
+
+ # The operation array_equal operation below effectively will force the array
+ # into memory.
+ # So we cache it here.
+ self._data = np.asarray(self._data)
_data, other = np.broadcast_arrays(self._data, other)
return np.array_equal(
self._data,
Activity
Cache the result of the zero-check
Cache the result of the zero-check
Cache the result of the zero-check
[-]zero check in encoding less than idea for lazy arrays[/-][+]zero check in encoding less than ideal for lazy arrays[/+]d-v-b commentedon Dec 30, 2025
I see a few options for working around this limitation:
my_zarr_array[:] = my_array_likeand instead promoting APIs that give more control over the chunk encoding process. Then you could simply encode the chunks you want to encode without extra indirection.hmaarrfk commentedon Dec 30, 2025
One of the aspects that has stopped me from pushing forward with the recommendations you made in #3628 is that I currently use zarr through xarray. So i'll have to ensure I understand how to configure
zarrthrough thexarraylens, and I just haven't found the time to do that. I'm also not concentrating on the fully disastrous usecase that I outline below. So for me, the cost is at most "2x" read, and not "100x" read. For the case I outline below, I'll likely just flag "write 0 chunks", and I don't think we will be storing with zarr (at least to start).this is nice, and may be good compromise, I am worried about "unecessary memory allocation and deallocation" for performance reasons.
To help make the problem feel "real", I wanted to provide a minimum reproducible example of the codepath i'm concerned with.
I created a "minimal" array-like class that mimics an array that doesn't support random access.
The array is pretty "inexpensive" to read once, and in order, but once you try to revisit data that has already been written, its pretty disastrous. Fun game, let me know when you stop waiting, I pressed alt-tab like 4 seconds in. My attention span is pretty short....
Array API version
xref: imageio/imageio#933
d-v-b commentedon Jan 2, 2026
If you need fine-grained control over writing your array, I would not do it through xarray. IIRC when you write an xarray data structure to zarr, you can declare whether you want to write the chunks or just prepare the array / group metadata documents. In your case, I would only use xarray for writing the metadata, and take control over writing the chunks with low-level control.
More broadly, the numpy API is not stateful w.r.t. iterating over elements of an array, and therefore the array API is not stateful wr.r.t array iteration, but your data is stateful, and in a surprising way (
x[0], x[0]is more than 2X slower thanx[0]for your data, iirc). I worry that this detail will cause a lot of friction / surprised users, and maybe a chunked array API (where each chunk conforms to the array API without any surprises, but fetching a chunk is slow) would be a better fit for your data.hmaarrfk commentedon Jan 2, 2026
Thats fair, i'm just explaining my personal constraints.
The truth about memory access, and hardware, is that any kind of memory access is stateful, and no truely "random access".
Cache, pre-fetching, memory alignment all play a role in performance.
Cache the result of the zero-check
d-v-b commentedon Jan 2, 2026
No matter what objective truth of memory is, the array API is broadly designed around the assumption that random access is fine. Your data, and IMO data stored in Zarr arrays, violates that assumption. So for these situations I think relying on the array API is questionable.
hmaarrfk commentedon Jan 2, 2026
i'm not sure i really follow why the conversation has gone down this path.
I would really like to bring it back to the issue at hand:
I'm trying to bring up a usability issue i've noticed in dask, and now in zarr.
d-v-b commentedon Jan 2, 2026
what is missing from the solution today of setting write_empty_chunks=True in the config?
hmaarrfk commentedon Jan 2, 2026
I'm not sure, i can't answer that right now in a way that is worthy of your time.
Please give me some time to study it more.
For today, this means that the "missing" part is ease of use:
Cache the result of the zero-check