Browse Source

ArrayPoolMemoryManager uses a different ArrayPool for large buffers + implemented ReleaseRetainedResources()

af/merge-core
Anton Firszov 8 years ago
parent
commit
73a20cb6de
  1. 20
      src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs
  2. 91
      src/ImageSharp/Memory/ArrayPoolMemoryManager.cs
  3. 3
      src/ImageSharp/Memory/BufferExtensions.cs
  4. 8
      src/ImageSharp/Memory/MemoryManager.cs
  5. 69
      tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs

20
src/ImageSharp/Memory/ArrayPoolMemoryManager.Buffer{T}.cs

@ -1,4 +1,8 @@
using System;
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
namespace SixLabors.ImageSharp.Memory
{
@ -10,15 +14,15 @@ namespace SixLabors.ImageSharp.Memory
private class Buffer<T> : IBuffer<T>
where T : struct
{
private readonly ArrayPoolMemoryManager memoryManager;
private readonly int length;
public Buffer(byte[] data, int length, ArrayPoolMemoryManager memoryManager)
private readonly ArrayPool<byte> sourcePool;
public Buffer(byte[] data, int length, ArrayPool<byte> sourcePool)
{
this.memoryManager = memoryManager;
this.Data = data;
this.length = length;
this.sourcePool = sourcePool;
}
protected byte[] Data { get; private set; }
@ -32,15 +36,15 @@ namespace SixLabors.ImageSharp.Memory
return;
}
this.memoryManager.pool.Return(this.Data);
this.sourcePool.Return(this.Data);
this.Data = null;
}
}
private class ManagedByteBuffer : Buffer<byte>, IManagedByteBuffer
{
public ManagedByteBuffer(byte[] data, int length, ArrayPoolMemoryManager memoryManager)
: base(data, length, memoryManager)
public ManagedByteBuffer(byte[] data, int length, ArrayPool<byte> sourcePool)
: base(data, length, sourcePool)
{
}

91
src/ImageSharp/Memory/ArrayPoolMemoryManager.cs

@ -1,27 +1,46 @@
using System.Buffers;
// Copyright (c) Six Labors and contributors.
// 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
{
using Guard = SixLabors.Guard;
/// <summary>
/// Implements <see cref="MemoryManager"/> by allocating memory from <see cref="ArrayPool{T}"/>.
/// </summary>
public partial class ArrayPoolMemoryManager : MemoryManager
{
/// <summary>
/// Defines the default maximum size of pooled arrays.
/// Currently set to a value equivalent to 16 MegaPixels of an <see cref="Rgba32"/> image.
/// The default value for: maximum size of pooled arrays in bytes.
/// Currently set to 32MB, which is equivalent to 8 megapixels of raw <see cref="Rgba32"/> data.
/// </summary>
internal const int DefaultMaxPooledBufferSizeInBytes = 32 * 1024 * 1024;
/// <summary>
/// The value for: The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.
/// </summary>
public const int DefaultMaxSizeInBytes = 4096 * 4096 * 4;
private const int DefaultLargeBufferThresholdInBytes = 8 * 1024 * 1024;
private readonly ArrayPool<byte> pool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for huge buffers, which is not kept clean.
/// </summary>
private ArrayPool<byte> largeArrayPool;
/// <summary>
/// The <see cref="ArrayPool{T}"/> for small-to-medium buffers which is not kept clean.
/// </summary>
private ArrayPool<byte> normalArrayPool;
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryManager"/> class.
/// </summary>
public ArrayPoolMemoryManager()
: this(DefaultMaxSizeInBytes)
: this(DefaultMaxPooledBufferSizeInBytes, DefaultLargeBufferThresholdInBytes)
{
}
@ -30,10 +49,40 @@ namespace SixLabors.ImageSharp.Memory
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
public ArrayPoolMemoryManager(int maxPoolSizeInBytes)
: this(maxPoolSizeInBytes, GetLargeBufferThresholdInBytes(maxPoolSizeInBytes))
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ArrayPoolMemoryManager"/> class.
/// </summary>
/// <param name="maxPoolSizeInBytes">The maximum size of pooled arrays. Arrays over the thershold are gonna be always allocated.</param>
/// <param name="largeBufferThresholdInBytes">The threshold to pool arrays in <see cref="largeArrayPool"/> which has less buckets for memory safety.</param>
public ArrayPoolMemoryManager(int maxPoolSizeInBytes, int largeBufferThresholdInBytes)
{
Guard.MustBeGreaterThan(maxPoolSizeInBytes, 0, nameof(maxPoolSizeInBytes));
Guard.MustBeLessThanOrEqualTo(largeBufferThresholdInBytes, maxPoolSizeInBytes, nameof(largeBufferThresholdInBytes));
this.MaxPoolSizeInBytes = maxPoolSizeInBytes;
this.LargeBufferThresholdInBytes = largeBufferThresholdInBytes;
this.pool = ArrayPool<byte>.Create(maxPoolSizeInBytes, 50);
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 LargeBufferThresholdInBytes { get; }
/// <inheritdoc />
public override void ReleaseRetainedResources()
{
this.InitArrayPools();
}
/// <inheritdoc />
@ -42,8 +91,10 @@ namespace SixLabors.ImageSharp.Memory
int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes;
byte[] byteBuffer = this.pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteBuffer, length, this);
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes);
var buffer = new Buffer<T>(byteArray, length, pool);
if (clear)
{
buffer.Clear();
@ -54,8 +105,10 @@ namespace SixLabors.ImageSharp.Memory
internal override IManagedByteBuffer AllocateManagedByteBuffer(int length, bool clear)
{
byte[] array = this.pool.Rent(length);
var buffer = new ManagedByteBuffer(array, length, this);
ArrayPool<byte> pool = this.GetArrayPool(length);
byte[] byteArray = pool.Rent(length);
var buffer = new ManagedByteBuffer(byteArray, length, pool);
if (clear)
{
buffer.Clear();
@ -63,5 +116,21 @@ namespace SixLabors.ImageSharp.Memory
return buffer;
}
private static int GetLargeBufferThresholdInBytes(int maxPoolSizeInBytes)
{
return maxPoolSizeInBytes / 4;
}
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
{
return bufferSizeInBytes <= this.LargeBufferThresholdInBytes ? this.normalArrayPool : this.largeArrayPool;
}
private void InitArrayPools()
{
this.largeArrayPool = ArrayPool<byte>.Create(this.MaxPoolSizeInBytes, 8);
this.normalArrayPool = ArrayPool<byte>.Create(this.LargeBufferThresholdInBytes, 24);
}
}
}

3
src/ImageSharp/Memory/BufferExtensions.cs

@ -1,3 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;

8
src/ImageSharp/Memory/MemoryManager.cs

@ -30,5 +30,13 @@ namespace SixLabors.ImageSharp.Memory
{
return new BasicArrayBuffer<T>(new T[length]);
}
/// <summary>
/// Releases all retained resources not being in use.
/// Eg: by resetting array pools and letting GC to free the arrays.
/// </summary>
public virtual void ReleaseRetainedResources()
{
}
}
}

69
tests/ImageSharp.Tests/Memory/ArrayPoolMemoryManagerTests.cs

@ -4,6 +4,7 @@
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -15,8 +16,10 @@ namespace SixLabors.ImageSharp.Tests.Memory
public class ArrayPoolMemoryManagerTests
{
private const int MaxPooledBufferSizeInBytes = 2048;
private MemoryManager MemoryManager { get; } = new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes);
private const int LargeBufferThresholdInBytes = MaxPooledBufferSizeInBytes / 2;
private MemoryManager MemoryManager { get; } = new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, LargeBufferThresholdInBytes);
/// <summary>
/// Rent a buffer -> return it -> re-rent -> verify if it's span points to the previous location
@ -38,11 +41,36 @@ namespace SixLabors.ImageSharp.Tests.Memory
public class BufferTests : BufferTestSuite
{
public BufferTests()
: base(new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes))
: base(new ArrayPoolMemoryManager(MaxPooledBufferSizeInBytes, LargeBufferThresholdInBytes))
{
}
}
public class Constructor
{
[Fact]
public void WhenBothParametersPassedByUser()
{
var mgr = new ArrayPoolMemoryManager(1111, 666);
Assert.Equal(1111, mgr.MaxPoolSizeInBytes);
Assert.Equal(666, mgr.LargeBufferThresholdInBytes);
}
[Fact]
public void WhenPassedOnly_MaxPooledBufferSizeInBytes_SmallerThresholdIsAutoCalculated()
{
var mgr = new ArrayPoolMemoryManager(5000);
Assert.Equal(5000, mgr.MaxPoolSizeInBytes);
Assert.True(mgr.LargeBufferThresholdInBytes < mgr.MaxPoolSizeInBytes);
}
[Fact]
public void When_LargeBufferThresholdInBytes_IsGreaterThan_MaxPooledBufferSizeInBytes_Throws()
{
Assert.ThrowsAny<Exception>(() => { new ArrayPoolMemoryManager(100, 200); });
}
}
[StructLayout(LayoutKind.Explicit, Size = MaxPooledBufferSizeInBytes / 5)]
struct LargeStruct
{
@ -98,5 +126,40 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.Equal(expected, secondAlloc.Span[0]);
}
}
[Fact]
public void ReleaseRetainedResources_ReplacesInnerArrayPool()
{
IBuffer<int> buffer = this.MemoryManager.Allocate<int>(32);
ref int ptrToPrev0 = ref buffer.Span.DangerousGetPinnableReference();
buffer.Dispose();
this.MemoryManager.ReleaseRetainedResources();
buffer = this.MemoryManager.Allocate<int>(32);
Assert.False(Unsafe.AreSame(ref ptrToPrev0, ref buffer.DangerousGetPinnableReference()));
}
[Fact]
public void ReleaseRetainedResources_DisposingPreviouslyAllocatedBuffer_IsAllowed()
{
IBuffer<int> buffer = this.MemoryManager.Allocate<int>(32);
this.MemoryManager.ReleaseRetainedResources();
buffer.Dispose();
}
[Fact]
public void AllocationOverLargeArrayThreshold_UsesDifferentPool()
{
int arrayLengthThreshold = LargeBufferThresholdInBytes / sizeof(int);
IBuffer<int> small = this.MemoryManager.Allocate<int>(arrayLengthThreshold - 1);
ref int ptr2Small = ref small.DangerousGetPinnableReference();
small.Dispose();
IBuffer<int> large = this.MemoryManager.Allocate<int>(arrayLengthThreshold + 1);
Assert.False(Unsafe.AreSame(ref ptr2Small, ref large.DangerousGetPinnableReference()));
}
}
}
Loading…
Cancel
Save