Perf: use Stream.CopyTo and zero-copy MemoryPackagePart reads#1743
Open
ken-swyfft wants to merge 2 commits intonissl-lab:masterfrom
Open
Perf: use Stream.CopyTo and zero-copy MemoryPackagePart reads#1743ken-swyfft wants to merge 2 commits intonissl-lab:masterfrom
ken-swyfft wants to merge 2 commits intonissl-lab:masterfrom
Conversation
…Part copy Replace manual 1KB buffer loop in StreamHelper.CopyStream with Stream.CopyTo() (80KB internal buffer). Eliminate redundant full-stream copy in MemoryPackagePart.GetInputStreamImpl by wrapping the existing buffer in a read-only MemoryStream (zero-copy). Benchmarked: 29% reduction in memory allocations during Write operations (103MB → 73MB on test-performance.xlsx). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ken-swyfft
commented
Mar 21, 2026
Contributor
Author
ken-swyfft
left a comment
There was a problem hiding this comment.
Code Review
Overall: Approve. Both changes are correct and safe across all target frameworks.
StreamHelper.CopyStream → Stream.CopyTo()
Straightforward replacement. Internal buffer goes from 1KB to 80KB (the CopyTo default) — strictly better for the two remaining call sites (thumbnail operations). Dead totalRead variable correctly eliminated.
MemoryPackagePart.GetInputStreamImpl — zero-copy buffer view
The change from a full defensive copy to a read-only MemoryStream view over GetBuffer() is safe. I verified:
datais always constructed via the parameterlessnew MemoryStream()(resizable), soGetBuffer()won't throwUnauthorizedAccessException.- The returned stream is
writable: false, preventing accidental mutation. - All callers of
GetInputStream()only read from the stream (XML parsing,IOUtils.ToByteArray,IOUtils.Copy,Readloops). MemoryPackagePartOutputStreamalways replacesdatawithnew MemoryStream(), so outstanding views referencing the old buffer remain valid and are not corrupted.
Minor suggestion
Consider adding a brief comment on the GetBuffer() line explaining the safety invariants for future maintainers, e.g.:
// Return a read-only, non-copying view over the internal buffer.
// Safe because callers only read, and GetOutputStreamImpl() replaces data entirely.
return new MemoryStream(data.GetBuffer(), 0, (int)data.Length, writable: false);This makes the preconditions explicit so a future contributor doesn't accidentally break them (e.g., by constructing data from a byte[], which would make GetBuffer() throw).
Also noted
ZipPartMarshaller.Marshall(lines 54-67) has a similar manual buffer-copy loop (8KB buffer) that could benefit fromStream.CopyToas a follow-up.- Pre-existing potential null-ref in
MemoryPackagePart.Load()if called beforeGetOutputStreamImpl()— appears to be dead code, not introduced by this PR.
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.
Summary
Stream.CopyTo()which uses an 80KB internal buffer — available since .NET 4.0MemoryStreambuffer in a read-only view viaGetBuffer()(zero-copy)Benchmark results (test-performance.xlsx, 17MB, 5 sheets)
The Write benchmark shows the most significant improvement: 29% reduction in memory allocations (~30MB less per operation) from eliminating redundant
MemoryStreamcopies during package writes.Test plan
🤖 Generated with Claude Code