Describe the Feature
Currently, in Zarr v3 (zarr-python 3.x), the core encoding and decoding APIs in BatchedCodecPipeline (and individual codecs) are predominantly asynchronous (async def encode(), async def decode()).
However, many high-throughput data processing pipelines (e.g., converting large histopathology WSI images using pyvips/OpenCV in thread pools) run in pure synchronous, CPU-bound multi-threaded environments.
In these architectures, bridging the sync thread pool to async APIs via asyncio.run() introduces severe overhead. Based on our micro-benchmarks, running asyncio.run(pipeline.encode(...)) to compress 512x512 chunks:
- Async API via
asyncio.run(): ~1.65 ms/iter (nearly 0.8 ms is wasted on event loop creation/destruction per iteration)
- Direct internal sync encoding (
_encode_sync chain): ~0.65 ms/iter (2.5x faster than async bridging, 4.2x faster than creating ephemeral MemoryStore + zarr.Array templates)
Since underlying codecs already implement synchronous processing logic via _encode_sync(chunk_array, chunk_spec), it would be extremely beneficial to expose them as part of the public API (e.g., CodecPipeline.encode_sync or public Codec.encode_sync).
Proposed Solution
Expose public synchronous methods on Codec and CodecPipeline (or BatchedCodecPipeline):
class CodecPipeline:
def encode_sync(
self,
chunk_arrays_and_specs: Iterable[tuple[NDBuffer | None, ArraySpec]]
) -> Iterable[Buffer | None]:
...
This would allow CPU-bound, sync-heavy pipelines to directly leverage fast compression pipelines without any async event loop overhead.
Additional Context
Using undocumented internal _encode_sync methods works perfectly as a workaround but is prone to breakage across minor version upgrades of zarr-python.
Describe the Feature
Currently, in Zarr v3 (zarr-python 3.x), the core encoding and decoding APIs in
BatchedCodecPipeline(and individual codecs) are predominantly asynchronous (async def encode(),async def decode()).However, many high-throughput data processing pipelines (e.g., converting large histopathology WSI images using
pyvips/OpenCVin thread pools) run in pure synchronous, CPU-bound multi-threaded environments.In these architectures, bridging the sync thread pool to async APIs via
asyncio.run()introduces severe overhead. Based on our micro-benchmarks, runningasyncio.run(pipeline.encode(...))to compress 512x512 chunks:asyncio.run(): ~1.65 ms/iter (nearly 0.8 ms is wasted on event loop creation/destruction per iteration)_encode_syncchain): ~0.65 ms/iter (2.5x faster than async bridging, 4.2x faster than creating ephemeralMemoryStore+zarr.Arraytemplates)Since underlying codecs already implement synchronous processing logic via
_encode_sync(chunk_array, chunk_spec), it would be extremely beneficial to expose them as part of the public API (e.g.,CodecPipeline.encode_syncor publicCodec.encode_sync).Proposed Solution
Expose public synchronous methods on
CodecandCodecPipeline(orBatchedCodecPipeline):This would allow CPU-bound, sync-heavy pipelines to directly leverage fast compression pipelines without any async event loop overhead.
Additional Context
Using undocumented internal
_encode_syncmethods works perfectly as a workaround but is prone to breakage across minor version upgrades ofzarr-python.