-
Notifications
You must be signed in to change notification settings - Fork 495
Optimize BufferedSubStream.ReadByte #912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@@ -20,29 +20,41 @@ internal class BufferedSubStream(Stream stream, long origin, long bytesToRead) | |||
|
|||
public override void Flush() { } | |||
|
|||
public override long Length => BytesLeftToRead; | |||
public override long Length => BytesLeftToRead + _cacheLength - _cacheOffset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems weird that Length
in a stream refers only to the remaining portion, but since that's the way it already worked I assume it's for a reason.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe that's not the contract but I "meant" to do it 😬
|
||
public override long Position | ||
{ | ||
get => throw new NotSupportedException(); | ||
set => throw new NotSupportedException(); | ||
} | ||
|
||
private void RefillCache() | ||
{ | ||
var count = (int)Math.Min(BytesLeftToRead, _cache.Length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new bit of logic here; the old code would always request _cache.Length
bytes when refilling. It probably wasn't hurting anything but I figure it's better to request only what we need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Smells good, thanks!
@@ -20,29 +20,41 @@ internal class BufferedSubStream(Stream stream, long origin, long bytesToRead) | |||
|
|||
public override void Flush() { } | |||
|
|||
public override long Length => BytesLeftToRead; | |||
public override long Length => BytesLeftToRead + _cacheLength - _cacheOffset; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe that's not the contract but I "meant" to do it 😬
In
BufferedSubStream
, by changing whatBytesLeftToRead
and_cacheLength
track, we can simplify the work to be done when reading a byte.BytesLeftToRead
now tracks the amount of completely unread/uncached data, and_cacheLength
tracks the total cache length including the consumed portion. Then we only need to update the cache offset when reading a byte. It also moves the logic to refresh the cache into its own method. Interestingly, only about half of the performance increase comes from simplifying the variable usage. The other half comes from moving out the cache refresh logic, presumably because it makes theReadByte
method small enough for callers to inline.Core i7-6700k (1.7% reduction):
Apple M3 (2.8% reduction):
Time to extract a 14MB 7z. The benchmarks are with .NET 8.0 but it also helped slightly with .NET Framework 4.8 (~1.1% reduction).
I wish I could keep going with these optimization PRs but I think I'm out of ideas after this one 😄.