Summary
isal_zlib grows its output in a single PyBytes object that is doubled with _PyBytes_Resize() (i.e. realloc) as output accumulates:
Decompressing a 512 KiB payload reallocs the buffer 6 times (16K→32K→…→1M when the buffer fills exactly, plus the final shrink resize). Each step is a glibc realloc that either extends in place or moves+copies depending on what happens to neighbour the chunk, so:
- Avoidable copy traffic — up to ~1.5 MiB memcpy per 512 KiB decompressed in the worst case, and
- Nondeterministic performance — the copy volume depends on heap layout, i.e. on everything the process allocated before the call.
Testing against aiohttp's benchmark:
| backend |
cycle spread |
| stdlib zlib |
0.03% |
| isal.isal_zlib |
37.4% (22.9M–33.1M cycles) |
The entire delta between runs localizes to the memmove blob behind glibc realloc (ISA-L's own internal memmoves are bit-identical across runs, so this is purely the Python-side buffer management, not ISA-L).
Suggested fix
CPython removed exactly this pattern from zlibmodule.c/bz2/lzma in python/cpython#85658 (bpo-41486), merged as python/cpython#21740 for 3.10: _BlocksOutputBuffer keeps a list of bytes blocks and joins once at the end — "no overhead of resizing the buffer" — with measured speedups on top of the determinism. The header is self-contained and copyable: Include/internal/pycore_blocks_output_buffer.h, current usage in Modules/zlibmodule.c.
Summary
isal_zlib grows its output in a single PyBytes object that is doubled with _PyBytes_Resize() (i.e. realloc) as output accumulates:
Decompressing a 512 KiB payload reallocs the buffer 6 times (16K→32K→…→1M when the buffer fills exactly, plus the final shrink resize). Each step is a glibc realloc that either extends in place or moves+copies depending on what happens to neighbour the chunk, so:
Testing against aiohttp's benchmark:
The entire delta between runs localizes to the memmove blob behind glibc realloc (ISA-L's own internal memmoves are bit-identical across runs, so this is purely the Python-side buffer management, not ISA-L).
Suggested fix
CPython removed exactly this pattern from zlibmodule.c/bz2/lzma in python/cpython#85658 (bpo-41486), merged as python/cpython#21740 for 3.10: _BlocksOutputBuffer keeps a list of bytes blocks and joins once at the end — "no overhead of resizing the buffer" — with measured speedups on top of the determinism. The header is self-contained and copyable: Include/internal/pycore_blocks_output_buffer.h, current usage in Modules/zlibmodule.c.