Add a PooledMemoryStream to avoid allocating#1275
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces PooledMemoryStream (a MemoryStream-compatible implementation backed by ArrayPool<byte>) and refactors performance-critical code paths to use it instead of allocating new MemoryStream buffers, with accompanying unit tests and a small ThrowHelper addition.
Changes:
- Added
src/SharpCompress/IO/PooledMemoryStream.csplus a dedicated test suite inPooledMemoryStreamTests.cs. - Replaced various
MemoryStreamusages withPooledMemoryStreamacross TAR, 7z, LZMA2, PPMd, and Squeezed stream handling. - Added
ThrowHelper.ThrowIfGreaterThan(long, long, ...)and updatedpackages.lock.json(ILLink tasks versions).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/SharpCompress.Test/Streams/PooledMemoryStreamTests.cs | Adds behavioral tests for pooled buffer growth, disposal/pool returns, buffer exposure, capacity changes, and disposed behavior. |
| src/SharpCompress/IO/PooledMemoryStream.cs | Implements pooled/segmented + contiguous modes and overrides key MemoryStream APIs to reduce allocations. |
| src/SharpCompress/Writers/SevenZip/SevenZipWriter.cs | Uses PooledMemoryStream for temporary header streams during 7z finalization. |
| src/SharpCompress/Archives/Tar/TarArchive.cs | Uses PooledMemoryStream while decoding TAR header name data. |
| src/SharpCompress/Archives/Tar/TarArchive.Async.cs | Async equivalent TAR header decoding now uses PooledMemoryStream. |
| src/SharpCompress/Common/SevenZip/SevenZipFilesInfo.cs | Uses PooledMemoryStream for buffering 7z file property payloads. |
| src/SharpCompress/Common/SevenZip/ArchiveReader.cs | Replaces an internal MemoryStream allocation with PooledMemoryStream for 7z reading. |
| src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs | Uses PooledMemoryStream for temporary LZMA encode/decode buffers. |
| src/SharpCompress/Compressors/PPMd/PpmdStream.cs | Uses PooledMemoryStream in PPMd finalization path (EOF encode). |
| src/SharpCompress/Compressors/Squeezed/SqueezedStream.cs | Uses PooledMemoryStream for Huffman-decoded buffer and invalid-header empty stream. |
| src/SharpCompress/Compressors/Squeezed/SqueezedStream.Async.cs | Async Squeezed implementation mirrors PooledMemoryStream usage. |
| src/SharpCompress/ThrowHelper.cs | Adds a long overload for ThrowIfGreaterThan. |
| src/SharpCompress/packages.lock.json | Downgrades Microsoft.NET.ILLink.Tasks resolved/requested versions for net8.0 and net10.0. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… in PooledMemoryStream Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/1f2c3c48-1112-43f1-82ce-efb157fbe0d5 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…am/pooledmemorystream # Conflicts: # src/SharpCompress/IO/PooledMemoryStream.cs
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 10 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using var headerStream = new PooledMemoryStream(); | ||
| ArchiveHeaderWriter.WriteRawHeader(headerStream, mainStreamsInfo, filesInfo); |
There was a problem hiding this comment.
headerStream is now a PooledMemoryStream, but the raw-header path later computes a CRC via rawHeaderStream.GetBuffer().AsSpan(0, (int)Length). Since PooledMemoryStream.GetBuffer() allocates a fresh array sized to Capacity, this can introduce extra allocations proportional to capacity and undermine the pooling benefit. Consider switching CRC calculation to use a Length-sized buffer or a non-allocating path.
There was a problem hiding this comment.
Fixed in commit f8485b0. Both WriteCompressedHeader and WriteRawHeaderToOutput now compute the CRC by writing the stream's blocks directly into a Crc32Stream(Stream.Null) sink via WriteTo(), which avoids allocating any contiguous buffer.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/0ce9533f-f529-4c07-b641-6bf0883a0643 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 20 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| using var output = new PooledMemoryStream(); | ||
| decoder.Code(input, output, compressedData.Length, uncompressedSize, null); |
There was a problem hiding this comment.
FindConsumedBytes doesn’t use the decompressed output; it only needs input.Position after decoder.Code(...). Using PooledMemoryStream here adds pooling overhead and can rent/return buffers unnecessarily. Use Stream.Null (or another sink stream) for output instead.
| using var output = new PooledMemoryStream(); | |
| decoder.Code(input, output, compressedData.Length, uncompressedSize, null); | |
| decoder.Code(input, Stream.Null, compressedData.Length, uncompressedSize, null); |
Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/77f37cc4-3538-4df1-a816-aa7391065878 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
This pull request introduces the new
PooledMemoryStreamclass to the codebase and systematically replaces usages ofMemoryStreamwithPooledMemoryStreamin performance-critical paths. This change is aimed at reducing memory allocations and improving performance by reusing buffers. Additionally, comprehensive unit tests forPooledMemoryStreamhave been added, and a minor helper method was introduced inThrowHelper. There are also minor dependency downgrades in the project lock file.Memory Stream Refactoring:
Replaced all instances of
MemoryStreamwithPooledMemoryStreamin core archive and compression logic, including TAR, SevenZip, LZMA, PPMd, and Squeezed stream handling (TarArchive.cs,TarArchive.Async.cs,SevenZipFilesInfo.cs,ArchiveReader.cs,Lzma2EncoderStream.cs,PpmdStream.cs,SqueezedStream.cs,SqueezedStream.Async.cs,SevenZipWriter.cs). [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13]Added necessary
using SharpCompress.IO;directives to files usingPooledMemoryStream. [1] [2] [3] [4] [5]New Features and Testing:
PooledMemoryStreaminPooledMemoryStreamTests.cs, testing buffer management, disposal, capacity adjustments, and API behavior.Utilities:
ThrowIfGreaterThan(long value, long other, string? paramName = null)toThrowHelperfor improved argument validation.Dependency Updates:
Microsoft.NET.ILLink.Taskspackage versions inpackages.lock.jsonfor .NET 8.0 and .NET 10.0 targets. [1] [2]