Browse Source

Introduce ApplyOptions and use in allocators

pull/3120/head
James Jackson-South 2 months ago
parent
commit
2beb01baa6
  1. 6
      src/ImageSharp/Memory/AllocationTrackingState.cs
  2. 18
      src/ImageSharp/Memory/Allocators/MemoryAllocator.cs
  3. 14
      src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs
  4. 9
      src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs
  5. 2
      src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs
  6. 31
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs

6
src/ImageSharp/Memory/AllocationTrackingState.cs

@ -21,6 +21,12 @@ internal struct AllocationTrackingState
/// </summary>
/// <param name="allocator">The allocator that owns the reservation.</param>
/// <param name="lengthInBytes">The reserved allocation size, in bytes.</param>
/// <remarks>
/// Must complete-before the owning object's reference is observable to any other thread.
/// <see cref="MemoryAllocator"/> guarantees this by attaching synchronously on the allocating
/// thread before returning the owner; reference publication then provides the release fence
/// that makes these field writes visible to a subsequent <see cref="Release"/> on another thread.
/// </remarks>
internal void Attach(MemoryAllocator allocator, long lengthInBytes)
{
this.allocator = allocator;

18
src/ImageSharp/Memory/Allocators/MemoryAllocator.cs

@ -83,18 +83,26 @@ public abstract class MemoryAllocator
public static MemoryAllocator Create(MemoryAllocatorOptions options)
{
UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(options.MaximumPoolSizeMegabytes);
allocator.ApplyOptions(options);
return allocator;
}
/// <summary>
/// Applies the supplied <see cref="MemoryAllocatorOptions"/> to this instance.
/// </summary>
/// <param name="options">The options to apply. Properties left as <see langword="null"/> are ignored.</param>
private protected void ApplyOptions(MemoryAllocatorOptions options)
{
if (options.AllocationLimitMegabytes.HasValue)
{
allocator.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L;
allocator.SingleBufferAllocationLimitBytes = (int)Math.Min(allocator.SingleBufferAllocationLimitBytes, allocator.MemoryGroupAllocationLimitBytes);
this.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L;
this.SingleBufferAllocationLimitBytes = (int)Math.Min(this.SingleBufferAllocationLimitBytes, this.MemoryGroupAllocationLimitBytes);
}
if (options.AccumulativeAllocationLimitMegabytes.HasValue)
{
allocator.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L;
this.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L;
}
return allocator;
}
/// <summary>

14
src/ImageSharp/Memory/Allocators/SimpleGcMemoryAllocator.cs

@ -22,19 +22,7 @@ public sealed class SimpleGcMemoryAllocator : MemoryAllocator
/// Initializes a new instance of the <see cref="SimpleGcMemoryAllocator"/> class with custom limits.
/// </summary>
/// <param name="options">The <see cref="MemoryAllocatorOptions"/> to apply.</param>
public SimpleGcMemoryAllocator(MemoryAllocatorOptions options)
{
if (options.AllocationLimitMegabytes.HasValue)
{
this.MemoryGroupAllocationLimitBytes = options.AllocationLimitMegabytes.Value * 1024L * 1024L;
this.SingleBufferAllocationLimitBytes = (int)Math.Min(this.SingleBufferAllocationLimitBytes, this.MemoryGroupAllocationLimitBytes);
}
if (options.AccumulativeAllocationLimitMegabytes.HasValue)
{
this.AccumulativeAllocationLimitBytes = options.AccumulativeAllocationLimitMegabytes.Value * 1024L * 1024L;
}
}
public SimpleGcMemoryAllocator(MemoryAllocatorOptions options) => this.ApplyOptions(options);
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => int.MaxValue;

9
src/ImageSharp/Memory/Allocators/UniformUnmanagedMemoryPoolMemoryAllocator.cs

@ -70,6 +70,15 @@ internal sealed class UniformUnmanagedMemoryPoolMemoryAllocator : MemoryAllocato
this.nonPoolAllocator = new UnmanagedMemoryAllocator(unmanagedBufferSizeInBytes);
}
internal UniformUnmanagedMemoryPoolMemoryAllocator(
int sharedArrayPoolThresholdInBytes,
int poolBufferSizeInBytes,
long maxPoolSizeInBytes,
int unmanagedBufferSizeInBytes,
MemoryAllocatorOptions options)
: this(sharedArrayPoolThresholdInBytes, poolBufferSizeInBytes, maxPoolSizeInBytes, unmanagedBufferSizeInBytes)
=> this.ApplyOptions(options);
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => this.poolBufferSizeInBytes;

2
src/ImageSharp/Memory/DiscontiguousBuffers/MemoryGroup{T}.cs

@ -102,8 +102,8 @@ internal abstract partial class MemoryGroup<T> : IMemoryGroup<T>, IDisposable
int bufferAlignmentInElements,
AllocationOptions options = AllocationOptions.None)
{
int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes();
Guard.NotNull(allocator, nameof(allocator));
int bufferCapacityInBytes = allocator.GetBufferCapacityInBytes();
if (totalLengthInElements < 0)
{

31
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs

@ -477,6 +477,37 @@ public class UniformUnmanagedPoolMemoryAllocatorTests
allocator.AllocateGroup<byte>(oneMb, 1024).Dispose();
}
[Fact]
public void AllocateGroup_AccumulativeLimit_NonPoolFallback_TracksOncePerGroup()
{
// Configure the pool with zero capacity so multi-segment requests bypass both the
// single-buffer-from-pool path and MemoryGroup<T>.TryAllocate(pool, ...) and fall
// through to MemoryGroup<T>.Allocate(nonPoolAllocator, ...). The unmanaged segment
// size is small enough that the request must span multiple segments, which is the
// path where per-segment double-counting could regress.
UniformUnmanagedMemoryPoolMemoryAllocator allocator = new(
sharedArrayPoolThresholdInBytes: 64 * 1024,
poolBufferSizeInBytes: 128 * 1024,
maxPoolSizeInBytes: 0,
unmanagedBufferSizeInBytes: 256 * 1024,
new MemoryAllocatorOptions { AccumulativeAllocationLimitMegabytes = 1 });
// 768 KB exceeds the pool buffer size, so the request takes the multi-segment
// non-pool fallback (three 256 KB segments). If tracking double-counted (group
// plus each segment), reservation would be 768 KB + 768 KB = 1.5 MB and exceed
// the 1 MB limit on allocation itself.
MemoryGroup<byte> g = allocator.AllocateGroup<byte>(768 * 1024, 1024);
Assert.True(g.Count > 1, "Test setup must exercise the multi-segment fallback path.");
// Reservation should be exactly 768 KB; another 512 KB would push to 1.25 MB and throw.
Assert.Throws<InvalidMemoryOperationException>(() => allocator.Allocate<byte>(512 * 1024));
g.Dispose();
// After disposal the reservation is fully released; a second equivalent group succeeds.
allocator.AllocateGroup<byte>(768 * 1024, 1024).Dispose();
}
[ConditionalFact(typeof(Environment), nameof(Environment.Is64BitProcess))]
public void MemoryAllocator_Create_SetHighLimit()
{

Loading…
Cancel
Save