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