Repository navigation
gh-160310: Decompress a single stream directly in _decompress_all() - #161737
Open
maikschulze wants to merge 1 commit into
Open
maikschulze wants to merge 1 commit into
maikschulze wants to merge 1 commit into
Conversation
…ll() Since pythongh-160310, every one-shot bz2, lzma and zstd decompress() call wraps the data in memoryviews and collects the output in lists, also for the common case of a single stream, which costs about 0.4 us per call (a quarter of a zstd call on 1 KiB). Decompress the data with the first decompressor directly, and only take the loop for data that holds more than one stream. This also restores the exceptions of the released versions for non-contiguous buffers and non-buffer arguments.
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.
Follow-up to gh-160310 (#160311): with that fix, one-shot
bz2,lzmaandzstddecompress()of a single stream also goes through the memoryviews and lists that keep the many-stream case linear. This adds a fast path for the single stream:_decompress_all()decompresses the data with the first decompressor directly and returns its result if that was the only stream. Only data with more streams takes the loop, starting after the first one, so the many-stream case stays linear, and small inputs get most of their previous speed back (decompress()throughput, MB/s):gzip has its own loop, which gh-160310 changed too and this PR leaves alone: main and this PR run the same gzip code, so their difference (up to 2%) is the noise of the measurement. bz2 and lzma gain less, as their decoders take most of the call; for larger inputs, the difference is lost in the noise.
macOS 26.7.1 on an Apple M4 Pro, Linux arm64 in Docker on the same machine (Debian trixie, GCC 14), Windows 11 on a Ryzen 9 7950X (Visual Studio 17.11); PGO builds of main, with the script below, the best of 7 alternating runs per state. Each platform uses one build for all three states: main runs with the build's own standard library; for the other two,
PYTHONPATHpoints to a copy ofgzip.py,bz2.py,lzma.pyand thecompressionpackage as of e80962a's parent (c3fe3c3b85, "before" in the tables) or as of this PR. On Windows, where the bz2 and lzma decompressors alone ran at different speeds from one process to the next, the three states take turns in one process instead, with the second script below.Where the time goes
The decompressor's share of each
decompress()call:The decompressor alone is created and used once per call, as
decompress()does; gzip's iszlib.decompressobj(wbits=31). The rest of the call is Python around it. What zstd and lzma don't get back comes from passing their keyword arguments through_decompress_all(**decomp_args); bz2 passes none and is back where it was.With many streams, the fast path hands over to the same loop as main after the first stream, so gh-160310's own reproducer is as fast as on main:
gh-160310's reproducer
Results and exceptions are unchanged, except for two invalid inputs, which raise what the released versions raise again:
BufferErrorinstead ofTypeErrorfor a non-contiguous memoryview, and for astr,TypeErrorwithout "memoryview: " in its message (#160311 mentions this as a side effect). The compression tests pass on macOS, Linux and Windows.Since gh-160310 is in the 3.14 and 3.15 branches, this might fit the same backports.
cc @gpshead
Script
Second script: all three states in one process (Windows)
I used an AI assistant to investigate this and draft the patch; I reviewed and tested the changes myself.