-
-
Notifications
You must be signed in to change notification settings - Fork 854
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
Updating some readonly static data in JpegEncoderCore to take advantage of compiler functionality. #855
Updating some readonly static data in JpegEncoderCore to take advantage of compiler functionality. #855
Conversation
Before:
After:
|
Codecov Report
@@ Coverage Diff @@
## master #855 +/- ##
=======================================
Coverage 88.89% 88.89%
=======================================
Files 1014 1014
Lines 44295 44295
Branches 3208 3209 +1
=======================================
Hits 39376 39376
Misses 4198 4198
Partials 721 721
Continue to review full report at Codecov.
|
// This is a port of the CoreFX implementation and is MIT Licensed: https://github.com/dotnet/coreclr/blob/c4dca1072d15bdda64c754ad1ea474b1580fa554/src/System.Private.CoreLib/shared/System/IO/Stream.cs#L770 | ||
public static void Write(this Stream stream, ReadOnlySpan<byte> buffer) | ||
{ | ||
byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.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.
We usually prefer using our own memory management primitives like MemoryAllocator.AllocateManagedByteBuffer()
instead of ArrayPool.Shared
.
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 matching a signature and implementation available in .NET Core. ArrayPool<byte>.Shared
should generally be allocation-less for something like this (not necessarily for other T
) since most of the framework is fairly dependent on it.
I'll run benchmarks on net472 as well though, in order to see what the overhead here is, if any, since it isn't applicable to netcoreapp2.1 (where the above benchmark was run).
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.
Our "allocator" is typically also an allocation free pool, but we definitely have an overhead because of nested virtual calls, and other infrastructure stuff.
Everything depends on the size of the array. It's not uncommon to have very large (> 1 MB) buffers in image processing, which works better with our allocator/pool in my experience.
However, for those buffers we should avoid invoking this extension method anyways. @JimBobSquarePants we can probably make an exception here, but we need to make sure the purpose is documented, or at least we remember it 😄
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.
Definitely and I don't recall if benchmark.net isolates each iteration or not (I know it does for separate tests), so it could be that the benchmark is "lying" for full framework and it is hiding the allocation cost (so it might not be representative of real world scenarios).
It would be possible to take a MemoryAllocator
as an optional parameter or to not have SosHeaderYCbCr
take advantage of this compiler optimization (the single copy on class instantiation isn't terrible, but losing out on permanent pinning is a bit unfortunate) or to only not use the optimization on full framework if there are concerns about this regressing full framework.
IIRC, the ArrayPool<byte>.Shared
is optimized for up to 2MB, since we use it for things like the JSON and XML readers.
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.
Let's add a comment explaining why we use that pool (signature matching) and move on.
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.
Fixed. Also rebased onto the current HEAD.
byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length); | ||
try | ||
{ | ||
buffer.CopyTo(sharedBuffer); |
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.
To me it looks like we are loosing all the benefits of the copy-free initialization at this line...
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.
But the benchmarks show different results ... I wonder why? I guess this method is not a hot path.
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.
In the case of netcoreapp, the method is actually virtual and various stream types can override it to avoid the copy. I'll double check the numbers on full framework to see if it hurts anything (and if so, will look at what can be done).
Before:
After:
|
…ge of compiler functionality.
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.
Amazing to see a change like this can yield such performance benefits. I'll need to learn more tricks like this!
Merging this in. Thanks @tannergooding |
Prerequisites
Description
This is a partial fix of #854 and shows how to take advantage of the underlying compiler functionality.