-
-
Notifications
You must be signed in to change notification settings - Fork 850
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
Configurable & optimized memory management #475
Conversation
- Add a null memory manager that doesn't do any actual memory management
code just before going to sleep
need it (aside from a few exceptions)
# Conflicts: # src/ImageSharp/Formats/Gif/GifDecoderCore.cs # src/ImageSharp/Formats/Png/PngEncoderCore.cs # src/ImageSharp/Image/ImageFrame{TPixel}.cs # src/ImageSharp/Memory/Buffer{T}.cs # src/ImageSharp/Memory/PixelDataPool{T}.cs # src/ImageSharp/Processing/Processors/Filters/LomographProcessor.cs # src/ImageSharp/Processing/Processors/Filters/PolaroidProcessor.cs # src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs # src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs # src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs # src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs # tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs # tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeProfilingBenchmarks.cs
…to feature/memory-manager # Conflicts: # tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
PngEncoder, WuQuantizer, ShapeRegion, ShapePath
@vpenades as you wish!
Yes, you always need to explicitly pass a memory manager to individual operations. I suggest you to implement an |
@antonfirsov Upon a second review, I've realized the Configuration class essentially handles memory managers and image formats, but Image Formats are handled directly by the configuration class. Maybe it could be cleaner to have a image formats decoupled from That would make life easier when creating multiple configurations while keeping a shared collection of image formats. with the current configuration other than the default, you need to manually scan for all the formats in the default and copy them into your own configuration. If at some point during execution a new format is added to the default configuration, the manually created configurations will not be aware of the change. Something like this:
This would allow having manually created configurations with their own formats, OR sharing the formats with the default configuration. |
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.
One hell of a job! 🎉
Only thing blocking a merge that I can see is the naming of GetReferenceToOrigo
. Despite it being internal, I'd rather fix it before so we don't forget.
buffer[i + offset] = array[i].X; | ||
} | ||
// TODO: This is a temporal workaround because of the lack of Span<T> API-s on IPath. We should use MemoryManager.Allocate() here! | ||
PointF[] innerBuffer = new PointF[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.
Let's get a PR in place against Shapes to add those API's.
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.
Should we do it now, or in a follow up PR pair?
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 do a follow up once we've done Shapes.
} | ||
} | ||
catch (Exception) | ||
{ | ||
// TODO: Why are we catching exceptions here silently ??? |
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 was probably a hangover from the old try..catch..finally
pattern from when we were directly using ArrayPool
this.valOffset = Buffer<short>.CreateClean(18); | ||
this.maxcode = Buffer<long>.CreateClean(18); | ||
// TODO: Replace FakeBuffer<T> usages with standard or array orfixed-sized arrays | ||
this.lookahead = memoryManager.AllocateFake<short>(256); |
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.
Are we leaving this TODO for when we PR Shapes?
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.
Yeah, that would allow us to get rid of the AllocateFake<T>
workaround entirely.
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.Colors[i] = color; | ||
} | ||
this.Colors = source.MemoryManager.Allocate<TPixel>(source.Width); | ||
this.Colors.Span.Fill(color); |
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.
Didn't know Span
had a Fill
method. Neat!
@@ -88,6 +88,14 @@ internal static Span<TPixel> GetPixelRowSpan<TPixel>(this Image<TPixel> source, | |||
where TPixel : struct, IPixel<TPixel> | |||
=> source.Frames.RootFrame.GetPixelRowSpan(row); | |||
|
|||
/// <summary> |
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.
When shall we make this and others public?
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 need Memory<T>
for the memory interop stuff, for the rest we need a proper API review. Not sure if stuff like .GetMemoryManager()
should be public.
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.
Cool, I'm looking forward to MS getting that released.
/// Gets a value indicating whether the area refers to the entire <see cref="DestinationBuffer"/> | ||
/// </summary> | ||
public bool IsFullBufferArea => this.Size == this.DestinationBuffer.Size(); | ||
|
||
/// <summary> | ||
/// Gets or sets a value at the given index. | ||
/// </summary> |
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.
Diff wouldn't let me highlight where I wanted to
GetReferenceToOrigo
I'm assuming this is a typo? GetReferenceToOrigin
perhaps?
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's just my Hunglish :D
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.
😄 Haha... Far better than any language skills I've accumulated.
@@ -221,9 +224,9 @@ private void ReadRle8<TPixel>(PixelAccessor<TPixel> pixels, byte[] colors, int w | |||
var color = default(TPixel); | |||
var rgba = new Rgba32(0, 0, 0, 255); | |||
|
|||
using (var buffer = Buffer2D<byte>.CreateClean(width, height)) | |||
using (var buffer = this.configuration.MemoryManager.Allocate2D<byte>(width, height, true)) |
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.
AllocateClean2D
?
@vpenades Looking at @antonfirsov Re throughput performance? Perhaps it's as simple as the abstraction costs due to using interfaces? I can't see anything that stands out. |
@JimBobSquarePants a quick initial investigation regarding the regression in speed: I've been running my benchmarks on .NET 4.6.1 runtime (was simpler to work with command line), where indexing Like it or not, but if we want the library to be fast on the classic .NET, we have to replace indexers with I will continue my investigation and maybe apply fixes on the weakest points. |
@antonfirsov I'm happy for this to be merged now. 👍
|
@JimBobSquarePants check out my latest optimization commits. Pushed stuff that would normally deserve a separate explanatory PR. Maybe I'll add a few more of these (only for stuff that's covered!), and we can merge afterwards. |
Ok, rock on, I'll leave merging to you. |
… pinning from OrigHuffmanTree (cherry picked from commit 81f6a94)
…ngs worse! (cherry picked from commit 0bf64a09598ba988318c170259a7aab4d9391cb5)
Okkay, I guess I managed to bring back speed to previous levels with just a few changes, so no one would feel there's a regression. |
That's a neat trick you used to access the fixed |
@@ -58,10 +58,12 @@ internal struct Bytes : IDisposable | |||
/// <returns>The bytes created</returns> | |||
public static Bytes Create(MemoryManager memoryManager) |
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.
No need to pass the parameter here now or to the InputProcessor.
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.
Thanks for fixing it!
@JimBobSquarePants Re indexable fixed structs: |
…/ImageSharp into feature/memory-manager
@JimBobSquarePants Yes, please, this PR is a bit useless if creating manual configurations is non trivial. |
I'm merging this now, further optimizations and API changes will be added as separate PR-s. |
Configurable & optimized memory management
Prerequisites
PR summary
Changes
MemoryManager.Return()
in the final design, because the buffer implementations are now bound to theirMemoryManager
-s.Benchmarks
Load testing benchmarks have been implemented on the
load-test
branch. (The project should be moved into it's own SixLabors repository later). Link to resultsTLDR
This is how (managed/GC) memory consumption changes for 10,000 resize requests of ~3200p Jpegs, new defaults VS pre-MemoryManager (old):
The peaks are measured around requests with big outlier images.
Throughput
The throughput visualized for different scenarios & pool settings (measured in MegaPixel/sec):
Memory footprint
Avarage managed memory footprint while serving ~600 resize requests (different scenarios & pool settings):
Conclusions
ArrayPoolMemoryManager.CreateWithModeratePooling()
or.CreateWithMinimalPooling()
! For these two: the difference in memory consumption is not as big as the difference in throughput.SimpleGCMemoryManager
("NullMemoryManager"), it only makes things worse!ArrayPoolMemoryManager.CreateWithAggressivePooling()
can increase the throughput with the cost of a higher memory footprint/cc @vpenades @GeorgePlotnikov @denisivan0v