-
-
Notifications
You must be signed in to change notification settings - Fork 849
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
WIP. More Efficient MemoryAllocator #1660
Closed
Closed
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
8da4092
Add GC handling, use shared, simplify pool
JimBobSquarePants 1a0ce6f
Only assign reference if using the large pool
JimBobSquarePants 407092b
Fix null ref
JimBobSquarePants ad96db7
Introduce a timer that performs a callback to cleanup
JimBobSquarePants 6306567
Pass allocator directly
JimBobSquarePants a9ee629
Update ArrayPoolMemoryAllocator.Buffer{T}.cs
JimBobSquarePants 6036a39
Use GC Aware configurable pool.
JimBobSquarePants c860447
Simplify and use 2MB buffers
JimBobSquarePants e9105d3
Fix iterator tests
JimBobSquarePants 4feb673
Add unmanaged buffer implementation
JimBobSquarePants 0de66a4
Cleanup and fix warnings
JimBobSquarePants f9395fd
Fix everything but Tiff
JimBobSquarePants 5e51795
Merge branch 'master' into js/memory-experiments
JimBobSquarePants 801e887
Merge branch 'master' into js/memory-experiments
JimBobSquarePants 06f9557
Merge branch 'master' into js/memory-experiments
JimBobSquarePants 0aa3211
Better naming, fix last failing test
JimBobSquarePants 73bd60a
Remove factory methods
JimBobSquarePants f77923a
Merge branch 'master' into js/memory-experiments
JimBobSquarePants ce668dd
Clean up utilities and namespaces.
JimBobSquarePants 65d2a28
Remove IManagedByteBuffer
JimBobSquarePants 88adace
Fix all but 2 bitmap decode tests
JimBobSquarePants 9c51119
Fix bmp tests, re-enable dither tests
JimBobSquarePants 4c4f9d3
Merge branch 'master' into js/memory-experiments
JimBobSquarePants 0e01d1b
Skip failing test on non windows
JimBobSquarePants b18c871
Skip another assertation on UNIX
JimBobSquarePants e9e1948
Update ImageSharp.sln
JimBobSquarePants 65c67d3
Smarter pixel map reuse.
JimBobSquarePants a22b0cb
Skip flaky test
JimBobSquarePants f01bc0a
Fix first ticks.
JimBobSquarePants 547614a
Skip flaky test
JimBobSquarePants File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains 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
File renamed without changes.
94 changes: 94 additions & 0 deletions
94
src/ImageSharp/Memory/Allocators/Internals/UnmanagedBuffer{T}.cs
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright (c) Six Labors. | ||
// Licensed under the Apache License, Version 2.0. | ||
|
||
using System; | ||
using System.Buffers; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace SixLabors.ImageSharp.Memory.Internals | ||
{ | ||
/// <summary> | ||
/// Allocates and provides an <see cref="IMemoryOwner{T}"/> implementation giving | ||
/// access to unmanaged buffers. | ||
/// </summary> | ||
/// <typeparam name="T">The element type.</typeparam> | ||
internal sealed class UnmanagedBuffer<T> : MemoryManager<T> | ||
where T : struct | ||
{ | ||
private bool isDisposed; | ||
private readonly SafeHandle safeHandle; | ||
private readonly int length; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="UnmanagedBuffer{T}"/> class. | ||
/// </summary> | ||
/// <param name="byteCount">The number of bytes to allocate.</param> | ||
public UnmanagedBuffer(int byteCount) | ||
{ | ||
this.length = byteCount / Unsafe.SizeOf<T>(); | ||
this.safeHandle = new SafeHGlobalHandle(byteCount); | ||
} | ||
|
||
public override unsafe Span<T> GetSpan() | ||
=> new Span<T>((void*)this.safeHandle.DangerousGetHandle(), this.length); | ||
|
||
/// <inheritdoc /> | ||
public override unsafe MemoryHandle Pin(int elementIndex = 0) | ||
{ | ||
// Will be released in Unpin | ||
bool unused = false; | ||
this.safeHandle.DangerousAddRef(ref unused); | ||
|
||
void* pbData = Unsafe.Add<T>((void*)this.safeHandle.DangerousGetHandle(), elementIndex); | ||
return new MemoryHandle(pbData); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override void Unpin() => this.safeHandle.DangerousRelease(); | ||
|
||
/// <inheritdoc /> | ||
protected override void Dispose(bool disposing) | ||
{ | ||
if (this.isDisposed || this.safeHandle.IsInvalid) | ||
{ | ||
return; | ||
} | ||
|
||
if (disposing) | ||
{ | ||
this.safeHandle.Dispose(); | ||
} | ||
|
||
this.isDisposed = true; | ||
} | ||
|
||
private sealed class SafeHGlobalHandle : SafeHandle | ||
{ | ||
private readonly int byteCount; | ||
|
||
public SafeHGlobalHandle(int size) | ||
: base(IntPtr.Zero, true) | ||
{ | ||
this.SetHandle(Marshal.AllocHGlobal(size)); | ||
GC.AddMemoryPressure(this.byteCount = size); | ||
} | ||
|
||
public override bool IsInvalid => this.handle == IntPtr.Zero; | ||
|
||
protected override bool ReleaseHandle() | ||
{ | ||
if (this.IsInvalid) | ||
{ | ||
return false; | ||
} | ||
|
||
Marshal.FreeHGlobal(this.handle); | ||
GC.RemoveMemoryPressure(this.byteCount); | ||
this.handle = IntPtr.Zero; | ||
|
||
return true; | ||
} | ||
} | ||
} | ||
} |
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.
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 this be done? This would lead to a faster gen2 collection and object itself would behave like it's a managed allocation which I assume is not what we want to be happening for large chunks of data.
Microsoft docs states that "The AddMemoryPressure and RemoveMemoryPressure methods improve performance only for types that exclusively depend on finalizers to release the unmanaged resources". Which is not the case here as safe handle is used to prevent memory leaking only when user forgot to call dispose which is very unlikely to be caused by library code (this should be tested somehow to be honest).
Otherwise this would lead to a memory throttle as it is with current implementation.
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.
That advice is awkwardly worded. In the case where a user fails to Dispose an object backed by an unmanaged buffer, it does rely on the SafeHandle's finalizer to release the memory, so it is advantageous to advise the GC that it might be able to reclaim the memory by doing its GC thing. This should only result in more frequent gen2 GCs if the memory limits are being reached, which should only happen if the unmanaged buffer is long-lived or if the system is actually low on memory. For properly Disposed ephemeral buffers, the GC will be aware of the added memory pressure but will also see it freed quickly, so no harm.
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.
Not sure that these buffers can be named 'ephemeral', 2mb is a lot (at least it looks like a lot for something temporal) which should lead to a lot of execution time spent on this memory. Primary idea I had while writing this is that even if user forgets to dispose memory backed by a handle it'll be freed by either gen0/gen1 collection or full gen2 collection when
OutOfMemoryException
kicks in.While I'm not so sure about this now, I'm still concerned that this might be an overkill due to freachable queue. At least I'd want to benchmark this on something big like your parallel beeheads demo.
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.
unmanaged buffers will only be used for allocations that are over the pooled size limit. They should be used once and then released, hence 'ephemeral'. The question of whether
GC.AddMemoryPressure
is appropriate ultimately comes down to "is there any way a GC could free this memory?". The answer here is yes, but only if the memory has been leaked because someone didn't dispose. Unfortunately there's no way to know whether someone is going to leak; you can only tell after they've done it, when your finalizer runs.What the MS docs should have said is something along the lines of "don't tell the GC about memory it couldn't possibly reclaim". If the allocation and free were always 100% deterministic, there would be no point in telling the GC about them. But since we can't know whether the GC could reclaim the memory, it's better to tell it about the allocation than to not.
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.
Once again, thanks for a deep dive! I've never looked at GC from "memory it can or cannot not claim" point of view.