GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Faster decompression of gzip files · Issue #95534 · python/cpython

Faster decompression of gzip files #95534

New issue
Copy link
New issue
Copy link
Closed
Closed
Faster decompression of gzip files#95534
Copy link
Labels
3.12only security fixesonly security fixesperformancePerformance or resource usagePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

Description

@rhpvorderman
rhpvorderman
opened on Aug 1, 2022
Issue body actions

Pitch

Decompressing gzip streams is an extremely common practice. Most web browsers support gzip decompression, as such most (virtually all) servers return gzip compressed data (when the gzip support is advertised via headers). Tar.gz files are an extremely common way to archive files. Zip files use internal gzip compression.

Speeding this up by a non-trivial amount is therefore very advantageous.

Feature or enhancement

The current gzip reading pipeline can be improved quite a lot. This is the current way of doing things:

  • read io.DEFAULT_BUFFER_SIZE of data from a _PaddedFile object
  • feed it to a zlib.decompressobj() using the decompress(raw_data, size) function.
  • Internally decompress always starts with a 16KB buffer, regardless of the requested size. When the output data is 64KB big, it will need to resize 2 times.
  • The decompressed data is returned, anything not returned is saved in an unconsumed_tail object.
  • The unconsumed_tail is used to rewind the _PaddedFile object to the correct position.
  • The decompressed data length and crc32 are taken and these are used to update the GzipReader state.

This has some severe disadvantages when reading large blocks:

  • Gzip compresses between 50 and 90% in most cases. This means that the decompress function is going to return anywhere between 16 and 80KB maximum. When 128 KB is requested from the read function this means the 128 KB is not going to be filled, leading to unnecessary read requests.
  • In the above case say the typical return size is 37kb. That means due to the DEFAULT_BUFFER_SIZE in zlib being 16KB there will be two calls to resize the memory of the return object. (16->32->64). EDIT: actually it is three, since the end product is also resized to fit the contents (16->32->64->37).

This also has some severe disadvantages when reading small blocks.

  • When reading individual lines from a file (quite common) this actually queries a io.BufferedReader instance which reads from the _GzipReader in io.DEFAULT_BUFFER_SIZE chunks.
  • This means only 8KB requested. But still, 8KB is read from the _PaddedFile object. With typical compression rates this means anywhere between 4KB to 7KB of unconsumed tail will be returned. This creates a new object. Meaning allocating memory again. The same data is reread in the next iteration. This means in case of a 70% decompression rate, the same data is memcpy'd around 2 to 3 times.
  • Continuous rewinding of the _PaddedFile object

How to improve this:

  • Use the structure of the _bz2.Bz2Decompressor. Submitted data is copied to an internal buffer, data from this internal buffer is used to return decompress calls. This has the advantage of not endlessly allocating and destroing uncompressed_tail objects.
  • Read 128KB at once from the _PaddedFile object. And only read this data when the decompressor's needs_input attribute is True. This prevents querying the _PaddedFile object too much.
  • When decompress is called and a maxsize is given (say 128KB) allocate maxsize immediately, instead of allocating only 16KB and resizing later.

This prevents a lot of calls to the Python memory allocator.

This restructuring has already been implemented in python-isal. That project is a modification of zlibmodule.c to use the ISA-L optimizations. While this did improve speed, I also looked at other ways to improve the performance. By restructuring the gzip module and the zlib code the Python overhead was significantly reduced.

Relevant code:

  • _GzipReader.read function
  • Modified BZ2Decompressor code
  • Code that sets the buffer size to the maximum size

Most of this code can be seemlessly copied back into CPython. Which I will do when I have the time. This can best be done after the 3.11 release I think.

Previous discussion

NA. This is a performance enhancement, so not necessarily a new feature, but also not a bug.

Linked PRs

  • gh-95534: Improve gzip reading speed by 10% #97664
  • gh-95534: Convert ZlibDecompressor.__new__ to AC #137923
Reacted by Koki Saito

Activity

  1. rhpvorderman
    added
    type-featureA feature request or enhancementA feature request or enhancement
    on Aug 1, 2022
  2. gpshead commented on Aug 1, 2022

    @gpshead
    gpshead
    on Aug 1, 2022
    Member
    More actions

    PRs welcome!

    Reacted by Donghee Na
  3. corona10 commented on Aug 1, 2022

    @corona10
    corona10
    on Aug 1, 2022
    Member
    More actions

    PRs welcome!

    Plus, would you like to provide a microbenchmark by using pyperf?

  4. mdboom
    added
    performancePerformance or resource usagePerformance or resource usage
    on Aug 1, 2022
  5. rhpvorderman commented on Aug 2, 2022

    @rhpvorderman
    rhpvorderman
    on Aug 2, 2022
    ContributorAuthor
    More actions

    Thank you for your enthusiasm! I will put it on my todo list right away.

    Sure I can look and see if I can provide a microbenchmark.I have to say though: these changes really manifest when decompressing gzip files with bioinformatics data. Normal sizes for us are 10-20GB for Whole Exome Sequencing or RNA Sequencing, with ~100GB for Whole Genome Sequencing. So that is "Real-World" gzip data for me (you can see why this topic has my interest). That is not really "micro" though. I will look around for a suitable real-world case. I am currently thinking of decompressing tar.gz files.

  6. iritkatriel
    added
    3.12only security fixesonly security fixes
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Sep 24, 2022
  7. rhpvorderman
    added a commit that references this issue on Sep 30, 2022

    Merge branch 'main' into pythongh-95534

    22d3893
  8. rhpvorderman commented on Sep 30, 2022

    @rhpvorderman
    rhpvorderman
    on Sep 30, 2022
    ContributorAuthor
    More actions

    I made a PR. Microbenchmarks and results here for the interested:

    ./python -m pyperf timeit -s "import gzip; g=gzip.open('cpython-3.10.7.tar.gz', 'rb'); it=iter(lambda:g.read(128*1024), b'');" "for _ in it: pass"
    .....................
    Mean +- std dev: 301 ms +- 2 ms
    ./python -m pyperf timeit -s "import gzip; g=gzip.open('cpython-3.10.7.tar.gz', 'rb'); it=iter(lambda:g.read(128*1024), b'');" "for _ in it: pass"
    .....................
    Mean +- std dev: 270 ms +- 1 ms
    

    So a 10% performance improvement. Given that most of the work is done in the zlib library, this is a substantial reduction of overhead costs.

    Reacted by Marcel Martin and Koki Saito
  9. rhpvorderman
    added 2 commits that reference this issue on Sep 30, 2022

    Merge branch 'pythongh-95534' of github.com:rhpvorderman/cpython into p…

    d54c8b5

    Merge branch 'pythongh-95534' of github.com:rhpvorderman/cpython into p…

    9d60339
  10. gpshead
    added a commit that references this issue on Oct 17, 2022

    gh-95534: Improve gzip reading speed by 10% (#97664)

    eae7dad
  11. gpshead commented on Oct 17, 2022

    @gpshead
    gpshead
    on Oct 17, 2022
    Member
    More actions

    Thanks for doing this!

    Reacted by morotti
  12. gpshead
    closed this as completedon Oct 17, 2022
  13. carljm
    added a commit that references this issue on Oct 17, 2022

    Merge branch 'main' into typewatch

    cdbe93b
  14. AA-Turner
    added a commit that references this issue on Aug 19, 2025

    gh-95534: Convert ``ZlibDecompressor.__new__`` to AC (#137923)

    bb75dec
  15. Agent-Hellboy
    added a commit that references this issue on Aug 19, 2025

    pythongh-95534: Convert ``ZlibDecompressor.__new__`` to AC (python#13…

    f80e241
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixesonly security fixesperformancePerformance or resource usagePerformance or resource usagestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.