perf: skip BufReader for Stored files in make_reader#739
Conversation
Problem: by_index() allocates an 8 KiB BufReader and a Decompressor
for every entry, even CompressionMethod::Stored where decompression
is a no-op pass-through. For a 23K-entry JAR (mostly Stored files),
this means 2 heap allocations per call totaling 8,632 bytes each —
47,262 allocations just to iterate and drop.
Fix: add a ZipFileReader::Stored variant that wraps CryptoReader
directly in Crc32Reader, bypassing both BufReader and Decompressor.
This required adding a Debug impl for CryptoReader since the new
Stored variant needs it for the derive(Debug) on ZipFileReader.
Measured on bazel.jar (23,631 Stored entries):
by_index() allocations:
before: 2.0 allocs/entry, 8,632 bytes alloc'd each
after: 1.0 allocs/entry, 256 bytes alloc'd each
cargo bench read_entry (1 MiB Stored file):
before: 106,657 ns/iter (9,831 MB/s)
after: 101,276 ns/iter (10,353 MB/s, +5.3%)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refines the handling of uncompressed (Stored) files within the ZIP archive reading logic. By introducing a dedicated reader path for these files, it eliminates redundant buffering and decompression steps, leading to more efficient memory usage and faster data access. The changes are focused on optimizing resource consumption when dealing with files that do not require actual decompression. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
|
Warning Gemini is experiencing higher than usual traffic and was unable to create the review. Please try again in a few hours by commenting |
|
Hi Thanks for the MR I'm curious, which tools do you use to get this result? |
|
/Gemini review |
There was a problem hiding this comment.
Code Review
This pull request enhances the ZipFileReader in src/read.rs by introducing explicit handling for Stored (uncompressed) data. A new Stored variant is added to the ZipFileReader enum, along with corresponding implementations for the Read trait methods (read, read_exact, read_to_end, read_to_string) and the into_inner method. The make_reader function is updated to correctly construct a ZipFileReader::Stored when the compression method is Stored. Additionally, a Debug implementation was added for the CryptoReader enum to facilitate debugging.
|
@gemini-code-assist Will this cause a performance regression when reading from a file? |
|
Hi @Pr0methean, That's a great question! The benchmark results provided in the pull request description show a 5.3% improvement in read speed for 1 MiB However, it's important to consider that these benchmarks are just one data point, and performance may vary depending on the specific file being read, the hardware being used, and other factors. It would be helpful to run additional benchmarks on a wider variety of files and systems to get a more complete picture of the performance impact of these changes. Additionally, it's worth noting that the primary goal of this pull request is to reduce memory allocations, which can have a positive impact on overall performance even if read speed is not significantly affected. The reduced allocations can lead to less garbage collection overhead and improved memory locality, which can improve performance in memory-constrained environments. |
|
@gemini-code-assist Can this change increase the number of I/O syscalls? |
|
Hi @Pr0methean, That's another excellent question! Whether this change increases the number of I/O syscalls depends on a few factors:
To get a definitive answer, it would be necessary to:
In summary, it's possible that this change could increase the number of I/O syscalls in certain situations, but it's not guaranteed. Profiling and further analysis are needed to determine the actual impact. |
Currently, by_index() allocates an 8 KiB BufReader and a Decompressor for every entry, even when CompressionMethod is Stored (when decompression is a no-op pass-through). For a 23K-entry JAR (mostly Stored files), this means 2 heap allocations per call totaling 8,632 bytes each — 47,262 allocations just to iterate and drop.
Fix: add a ZipFileReader::Stored variant that wraps CryptoReader directly in Crc32Reader, bypassing both BufReader and Decompressor.
This required adding a Debug impl for CryptoReader since the new Stored variant needs it for the derive(Debug) on ZipFileReader.
Measured on bazel.jar (23,631 Stored entries):