diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs
index 6f4cef707..80c9c410e 100644
--- a/src/ImageSharp/Memory/PixelDataPool{T}.cs
+++ b/src/ImageSharp/Memory/PixelDataPool{T}.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
-using SixLabors.ImageSharp.PixelFormats;
+using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Memory
{
@@ -14,9 +14,35 @@ namespace SixLabors.ImageSharp.Memory
where T : struct
{
///
- /// The which is not kept clean.
+ /// The maximum size of pooled arrays in bytes.
+ /// Currently set to 32MB, which is equivalent to 8 megapixels of raw data.
///
- private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50);
+ internal const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024;
+
+ ///
+ /// The threshold to pool arrays in which has less buckets for memory safety.
+ ///
+ private const int LargeBufferThresholdInBytes = 8 * 1024 * 1024;
+
+ ///
+ /// The maximum array length of the .
+ ///
+ private static readonly int MaxLargeArrayLength = MaxPooledBufferSizeInBytes / Unsafe.SizeOf();
+
+ ///
+ /// The maximum array length of the .
+ ///
+ private static readonly int MaxNormalArrayLength = LargeBufferThresholdInBytes / Unsafe.SizeOf();
+
+ ///
+ /// The for huge buffers, which is not kept clean.
+ ///
+ private static readonly ArrayPool LargeArrayPool = ArrayPool.Create(MaxLargeArrayLength, 8);
+
+ ///
+ /// The for small-to-medium buffers which is not kept clean.
+ ///
+ private static readonly ArrayPool NormalArrayPool = ArrayPool.Create(MaxNormalArrayLength, 24);
///
/// Rents the pixel array from the pool.
@@ -25,7 +51,14 @@ namespace SixLabors.ImageSharp.Memory
/// The
public static T[] Rent(int minimumLength)
{
- return ArrayPool.Rent(minimumLength);
+ if (minimumLength <= MaxNormalArrayLength)
+ {
+ return NormalArrayPool.Rent(minimumLength);
+ }
+ else
+ {
+ return LargeArrayPool.Rent(minimumLength);
+ }
}
///
@@ -34,25 +67,13 @@ namespace SixLabors.ImageSharp.Memory
/// The array to return to the buffer pool.
public static void Return(T[] array)
{
- ArrayPool.Return(array);
- }
-
- ///
- /// Heuristically calculates a reasonable maxArrayLength value for the backing .
- ///
- /// The maxArrayLength value
- internal static int CalculateMaxArrayLength()
- {
- // ReSharper disable once SuspiciousTypeConversion.Global
- if (default(T) is IPixel)
+ if (array.Length <= MaxNormalArrayLength)
{
- const int MaximumExpectedImageSize = 16384 * 16384;
- return MaximumExpectedImageSize;
+ NormalArrayPool.Return(array);
}
else
{
- const int MaxArrayLength = 1024 * 1024; // Match default pool.
- return MaxArrayLength;
+ LargeArrayPool.Return(array);
}
}
}
diff --git a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs
index fdfd4c4b7..caba9a464 100644
--- a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs
+++ b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs
@@ -1,26 +1,33 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-
+using System.Linq;
+using System.Runtime.InteropServices;
+using SixLabors.ImageSharp.Memory;
+using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Memory
{
- using SixLabors.ImageSharp.Memory;
-
- using Xunit;
+ using System;
///
/// Tests the class.
///
public class PixelDataPoolTests
{
- [Fact]
- public void PixelDataPoolRentsMinimumSize()
+ private const int MaxPooledBufferSizeInBytes = PixelDataPool.MaxPooledBufferSizeInBytes;
+
+ readonly object monitor = new object();
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(1024)]
+ public void PixelDataPoolRentsMinimumSize(int size)
{
- Rgba32[] pixels = PixelDataPool.Rent(1024);
+ Rgba32[] pixels = PixelDataPool.Rent(size);
- Assert.True(pixels.Length >= 1024);
+ Assert.True(pixels.Length >= size);
}
[Fact]
@@ -33,23 +40,66 @@ namespace SixLabors.ImageSharp.Tests.Memory
Assert.True(pixels.Length >= 1024);
}
+ ///
+ /// Rent 'n' buffers -> return all -> re-rent, verify if there is at least one in common.
+ ///
+ private bool CheckIsPooled(int n, int count)
+ where T : struct
+ {
+ lock (this.monitor)
+ {
+ T[][] original = new T[n][];
+
+ for (int i = 0; i < n; i++)
+ {
+ original[i] = PixelDataPool.Rent(count);
+ }
+
+ for (int i = 0; i < n; i++)
+ {
+ PixelDataPool.Return(original[i]);
+ }
+
+ T[][] verification = new T[n][];
+
+ for (int i = 0; i < n; i++)
+ {
+ verification[i] = PixelDataPool.Rent(count);
+ }
+
+ return original.Intersect(verification).Any();
+ }
+ }
+
+ [Theory]
+ [InlineData(32)]
+ [InlineData(512)]
+ [InlineData(MaxPooledBufferSizeInBytes-1)]
+ public void SmallBuffersArePooled(int size)
+ {
+ Assert.True(this.CheckIsPooled(5, size));
+ }
+
[Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void CalculateMaxArrayLength(bool isRawData)
+ [InlineData(128 * 1024 * 1024)]
+ [InlineData(MaxPooledBufferSizeInBytes+1)]
+ public void LargeBuffersAreNotPooled_OfByte(int size)
{
- int max = isRawData ? PixelDataPool.CalculateMaxArrayLength()
- : PixelDataPool.CalculateMaxArrayLength();
+ Assert.False(this.CheckIsPooled(2, size));
+ }
- Assert.Equal(max > 1024 * 1024, !isRawData);
+ [StructLayout(LayoutKind.Explicit, Size = 512)]
+ struct TestStruct
+ {
}
[Fact]
- public void RentNonIPixelData()
+ public unsafe void LaregeBuffersAreNotPooled_OfBigValueType()
{
- byte[] data = PixelDataPool.Rent(16384);
+ const int mb128 = 128 * 1024 * 1024;
+ int count = mb128 / sizeof(TestStruct);
- Assert.True(data.Length >= 16384);
+ Assert.False(this.CheckIsPooled(2, count));
}
}
}
\ No newline at end of file