Browse Source

Remove ArrayPoolMemoryAllocator

af/UniformUnmanagedMemoryPoolMemoryAllocator-02-MemoryGuards
Anton Firszov 5 years ago
parent
commit
412ebfb8c6
  1. 91
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs
  2. 76
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs
  3. 166
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
  4. 21
      tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs
  5. 56
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

91
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.Buffer{T}.cs

@ -1,91 +0,0 @@
// 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;
using SixLabors.ImageSharp.Memory.Internals;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Contains <see cref="Buffer{T}"/>.
/// </summary>
public partial class ArrayPoolMemoryAllocator
{
/// <summary>
/// The buffer implementation of <see cref="ArrayPoolMemoryAllocator"/>.
/// </summary>
private class Buffer<T> : ManagedBufferBase<T>
where T : struct
{
/// <summary>
/// The length of the buffer.
/// </summary>
private readonly int length;
/// <summary>
/// A weak reference to the source pool.
/// </summary>
/// <remarks>
/// By using a weak reference here, we are making sure that array pools and their retained arrays are always GC-ed
/// after a call to <see cref="ArrayPoolMemoryAllocator.ReleaseRetainedResources"/>, regardless of having buffer instances still being in use.
/// </remarks>
private WeakReference<ArrayPool<byte>> sourcePoolReference;
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool)
{
this.Data = data;
this.length = length;
this.sourcePoolReference = new WeakReference<ArrayPool<byte>>(sourcePool);
}
/// <summary>
/// Gets the buffer as a byte array.
/// </summary>
protected byte[] Data { get; private set; }
/// <inheritdoc />
public override Span<T> GetSpan()
{
if (this.Data is null)
{
ThrowObjectDisposedException();
}
#if SUPPORTS_CREATESPAN
ref byte r0 = ref MemoryMarshal.GetReference<byte>(this.Data);
return MemoryMarshal.CreateSpan(ref Unsafe.As<byte, T>(ref r0), this.length);
#else
return MemoryMarshal.Cast<byte, T>(this.Data.AsSpan()).Slice(0, this.length);
#endif
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (!disposing || this.Data is null || this.sourcePoolReference is null)
{
return;
}
if (this.sourcePoolReference.TryGetTarget(out ArrayPool<byte> pool))
{
pool.Return(this.Data);
}
this.sourcePoolReference = null;
this.Data = null;
}
protected override object GetPinnableObject() => this.Data;
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException("ArrayPoolMemoryAllocator.Buffer<T>");
}
}
}
}

76
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.CommonFactoryMethods.cs

@ -1,76 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Contains common factory methods and configuration constants.
/// </summary>
public partial class ArrayPoolMemoryAllocator
{
/// <summary>
/// The default value for: maximum size of pooled arrays in bytes.
/// Currently set to 24MB, which is equivalent to 8 megapixels of raw RGBA32 data.
/// </summary>
internal const int DefaultMaxPooledBufferSizeInBytes = 24 * 1024 * 1024;
/// <summary>
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
private const int DefaultBufferSelectorThresholdInBytes = 8 * 1024 * 1024;
/// <summary>
/// The default bucket count for <see cref="largeArrayPool"/>.
/// </summary>
private const int DefaultLargePoolBucketCount = 6;
/// <summary>
/// The default bucket count for <see cref="normalArrayPool"/>.
/// </summary>
private const int DefaultNormalPoolBucketCount = 16;
// TODO: This value should be determined by benchmarking
private const int DefaultBufferCapacityInBytes = int.MaxValue / 4;
/// <summary>
/// This is the default. Should be good for most use cases.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateDefault()
{
return new ArrayPoolMemoryAllocator(
DefaultMaxPooledBufferSizeInBytes,
DefaultBufferSelectorThresholdInBytes,
DefaultLargePoolBucketCount,
DefaultNormalPoolBucketCount,
DefaultBufferCapacityInBytes);
}
/// <summary>
/// For environments with very limited memory capabilities, only small buffers like image rows are pooled.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateWithMinimalPooling()
{
return new ArrayPoolMemoryAllocator(64 * 1024, 32 * 1024, 8, 24);
}
/// <summary>
/// For environments with limited memory capabilities, only small array requests are pooled, which can result in reduced throughput.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateWithModeratePooling()
{
return new ArrayPoolMemoryAllocator(1024 * 1024, 32 * 1024, 16, 24);
}
/// <summary>
/// For environments where memory capabilities are not an issue, the maximum amount of array requests are pooled which results in optimal throughput.
/// </summary>
/// <returns>The memory manager.</returns>
public static ArrayPoolMemoryAllocator CreateWithAggressivePooling()
{
return new ArrayPoolMemoryAllocator(128 * 1024 * 1024, 32 * 1024 * 1024, 16, 32);
}
}
}

166
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs

@ -1,166 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
/// <summary>
/// Implements <see cref="MemoryAllocator"/> by allocating memory from <see cref="ArrayPool{T}"/>.
/// </summary>
[Obsolete("ArrayPoolMemoryAllocator is obsolete. Use MemoryAllocator.CreateDefault() instead.")]
public sealed partial class ArrayPoolMemoryAllocator : MemoryAllocator
{
private readonly int maxArraysPerBucketNormalPool;
private readonly int maxArraysPerBucketLargePool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
/// </summary>
private ArrayPool<byte> normalArrayPool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
/// </summary>
private ArrayPool<byte> largeArrayPool;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
public ArrayPoolMemoryAllocator()
: this(DefaultMaxPooledBufferSizeInBytes, DefaultBufferSelectorThresholdInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes)
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">Arrays over this threshold will be pooled in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
public ArrayPoolMemoryAllocator(int maxPoolSizeInBytes, int poolSelectorThresholdInBytes)
: this(maxPoolSizeInBytes, poolSelectorThresholdInBytes, DefaultLargePoolBucketCount, DefaultNormalPoolBucketCount)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
public ArrayPoolMemoryAllocator(
int maxPoolSizeInBytes,
int poolSelectorThresholdInBytes,
int maxArraysPerBucketLargePool,
int maxArraysPerBucketNormalPool)
: this(
maxPoolSizeInBytes,
poolSelectorThresholdInBytes,
maxArraysPerBucketLargePool,
maxArraysPerBucketNormalPool,
DefaultBufferCapacityInBytes)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryAllocator"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="poolSelectorThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
/// <param name="maxArraysPerBucketLargePool">Max arrays per bucket for the large array pool.</param>
/// <param name="maxArraysPerBucketNormalPool">Max arrays per bucket for the normal array pool.</param>
/// <param name="bufferCapacityInBytes">The length of the largest contiguous buffer that can be handled by this allocator instance.</param>
public ArrayPoolMemoryAllocator(
int maxPoolSizeInBytes,
int poolSelectorThresholdInBytes,
int maxArraysPerBucketLargePool,
int maxArraysPerBucketNormalPool,
int bufferCapacityInBytes)
{
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
Guard.MustBeLessThanOrEqualTo(poolSelectorThresholdInBytes, maxPoolSizeInBytes, nameof(poolSelectorThresholdInBytes));
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
this.PoolSelectorThresholdInBytes = poolSelectorThresholdInBytes;
this.BufferCapacityInBytes = bufferCapacityInBytes;
this.maxArraysPerBucketLargePool = maxArraysPerBucketLargePool;
this.maxArraysPerBucketNormalPool = maxArraysPerBucketNormalPool;
this.InitArrayPools();
}
/// <summary>
/// Gets the maximum size of pooled arrays in bytes.
/// </summary>
public int MaxPoolSizeInBytes { get; }
/// <summary>
/// Gets the threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
public int PoolSelectorThresholdInBytes { get; }
/// <summary>
/// Gets the length of the largest contiguous buffer that can be handled by this allocator instance.
/// </summary>
public int BufferCapacityInBytes { get; internal set; } // Setter is internal for easy configuration in tests
/// <inheritdoc />
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
/// <inheritdoc />
protected internal override int GetBufferCapacityInBytes() => this.BufferCapacityInBytes;
/// <inheritdoc />
public override IMemoryOwner<T> Allocate<T>(int length, AllocationOptions options = AllocationOptions.None)
{
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteArray, length, pool);
if (options.Has(AllocationOptions.Clean))
{
buffer.GetSpan().Clear();
}
return buffer;
}
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes) => maxPoolSizeInBytes / 4;
[MethodImpl(InliningOptions.ColdPath)]
private static void ThrowInvalidAllocationException<T>(int length, int max) =>
throw new InvalidMemoryOperationException(
$"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator.");
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
{
return bufferSizeInBytes <= this.PoolSelectorThresholdInBytes ? this.normalArrayPool : this.largeArrayPool;
}
private void InitArrayPools()
{
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, this.maxArraysPerBucketLargePool);
this.normalArrayPool = ArrayPool<byte>.Create(this.PoolSelectorThresholdInBytes, this.maxArraysPerBucketNormalPool);
}
}
}

21
tests/ImageSharp.Benchmarks/LoadResizeSave/LoadResizeSaveStressBenchmarks.cs

@ -17,11 +17,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
// private const JpegKind Filter = JpegKind.Progressive;
private const JpegKind Filter = JpegKind.Any;
#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete
private ArrayPoolMemoryAllocator arrayPoolMemoryAllocator;
#pragma warning restore CS0618
private MemoryAllocator defaultMemoryAllocator;
[GlobalSetup]
public void Setup()
{
@ -32,11 +27,6 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
};
Console.WriteLine($"ImageCount: {this.runner.ImageCount} Filter: {Filter}");
this.runner.Init();
this.defaultMemoryAllocator = Configuration.Default.MemoryAllocator;
#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete
this.arrayPoolMemoryAllocator = ArrayPoolMemoryAllocator.CreateDefault();
#pragma warning restore CS0618
}
private void ForEachImage(Action<string> action, int maxDegreeOfParallelism)
@ -59,17 +49,8 @@ namespace SixLabors.ImageSharp.Benchmarks.LoadResizeSave
[Benchmark(Baseline = true)]
[ArgumentsSource(nameof(ParallelismValues))]
public void ImageSharp_DefaultMemoryAllocator(int maxDegreeOfParallelism)
{
Configuration.Default.MemoryAllocator = this.defaultMemoryAllocator;
this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism);
}
[Benchmark]
[ArgumentsSource(nameof(ParallelismValues))]
public void ImageSharp_ArrayPoolMemoryAllocator(int maxDegreeOfParallelism)
public void ImageSharp(int maxDegreeOfParallelism)
{
Configuration.Default.MemoryAllocator = this.arrayPoolMemoryAllocator;
this.ForEachImage(this.runner.ImageSharpResize, maxDegreeOfParallelism);
}

56
tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

@ -192,20 +192,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
}
private enum AllocatorKind
{
Classic,
Unmanaged
}
private class CommandLineOptions
{
[Option('i', "imagesharp", Required = false, Default = false, HelpText = "Test ImageSharp without benchmark switching")]
public bool ImageSharp { get; set; }
[Option('a', "allocator", Required = false, Default = AllocatorKind.Unmanaged, HelpText = "Select allocator: Classic (ArrayPoolMemoryAllocator) or Unmanaged")]
public AllocatorKind Allocator { get; set; }
[Option('m', "max-contiguous", Required = false, Default = 4, HelpText = "Maximum size of contiguous pool buffers in MegaBytes")]
public int MaxContiguousPoolBufferMegaBytes { get; set; } = 4;
@ -253,40 +244,29 @@ namespace SixLabors.ImageSharp.Tests.ProfilingSandbox
}
public override string ToString() =>
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_a({this.Allocator})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})";
$"p({this.MaxDegreeOfParallelism})_i({this.ImageSharp})_m({this.MaxContiguousPoolBufferMegaBytes})_s({this.MaxPoolSizeMegaBytes})_u({this.MaxCapacityOfNonPoolBuffersMegaBytes})_r({this.RepeatCount})_g({this.FinalGcCount})_e({this.ReleaseRetainedResourcesAtEnd})";
public MemoryAllocator CreateMemoryAllocator()
{
switch (this.Allocator)
if (this.TrimTimeSeconds.HasValue)
{
case AllocatorKind.Classic:
#pragma warning disable CS0618 // 'ArrayPoolMemoryAllocator' is obsolete
return ArrayPoolMemoryAllocator.CreateDefault();
#pragma warning restore CS0618
case AllocatorKind.Unmanaged:
if (this.TrimTimeSeconds.HasValue)
return new UniformUnmanagedMemoryPoolMemoryAllocator(
1024 * 1024,
(int)B(this.MaxContiguousPoolBufferMegaBytes),
B(this.MaxPoolSizeMegaBytes),
(int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes),
new UniformUnmanagedMemoryPool.TrimSettings
{
return new UniformUnmanagedMemoryPoolMemoryAllocator(
1024 * 1024,
(int)B(this.MaxContiguousPoolBufferMegaBytes),
B(this.MaxPoolSizeMegaBytes),
(int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes),
new UniformUnmanagedMemoryPool.TrimSettings
{
TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000
});
}
else
{
return new UniformUnmanagedMemoryPoolMemoryAllocator(
1024 * 1024,
(int)B(this.MaxContiguousPoolBufferMegaBytes),
B(this.MaxPoolSizeMegaBytes),
(int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes));
}
default:
throw new ArgumentOutOfRangeException();
TrimPeriodMilliseconds = this.TrimTimeSeconds.Value * 1000
});
}
else
{
return new UniformUnmanagedMemoryPoolMemoryAllocator(
1024 * 1024,
(int)B(this.MaxContiguousPoolBufferMegaBytes),
B(this.MaxPoolSizeMegaBytes),
(int)B(this.MaxCapacityOfNonPoolBuffersMegaBytes));
}
}

Loading…
Cancel
Save