From 8edea8c53d442a67e673b132e79cf0d0563127f6 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 15 Jan 2018 23:29:43 +0100 Subject: [PATCH 1/2] PixelDataPool: reduce maximum pooled array size to 32MB for all value types --- src/ImageSharp/Memory/PixelDataPool{T}.cs | 34 ++++----- .../Memory/PixelDataPoolTests.cs | 70 +++++++++++++++---- 2 files changed, 68 insertions(+), 36 deletions(-) diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs index 6f4cef707..f25803951 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 { @@ -13,10 +13,21 @@ namespace SixLabors.ImageSharp.Memory internal class PixelDataPool where T : struct { + /// + /// The maximum size of pooled arrays in bytes. + /// Currently set to 32MB, which is equivalent to 8 megapixels of raw data. + /// + private const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024; + + /// + /// The maximum array length of the . + /// + private static readonly int MaxArrayLength = MaxPooledBufferSizeInBytes / Unsafe.SizeOf(); + /// /// The which is not kept clean. /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + private static readonly ArrayPool ArrayPool = ArrayPool.Create(MaxArrayLength, 50); /// /// Rents the pixel array from the pool. @@ -36,24 +47,5 @@ namespace SixLabors.ImageSharp.Memory { 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) - { - const int MaximumExpectedImageSize = 16384 * 16384; - return MaximumExpectedImageSize; - } - else - { - const int MaxArrayLength = 1024 * 1024; // Match default pool. - return MaxArrayLength; - } - } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs index fdfd4c4b7..beaa49dbc 100644 --- a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs @@ -1,20 +1,21 @@ // 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; - /// /// Tests the class. /// public class PixelDataPoolTests { + readonly object monitor = new object(); + [Fact] public void PixelDataPoolRentsMinimumSize() { @@ -33,23 +34,62 @@ namespace SixLabors.ImageSharp.Tests.Memory Assert.True(pixels.Length >= 1024); } - [Theory] - [InlineData(false)] - [InlineData(true)] - public void CalculateMaxArrayLength(bool isRawData) + /// + /// 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(); + } + } + + [Fact] + public void SmallBuffersArePooled() { - int max = isRawData ? PixelDataPool.CalculateMaxArrayLength() - : PixelDataPool.CalculateMaxArrayLength(); + Assert.True(this.CheckIsPooled(5, 512)); + } - Assert.Equal(max > 1024 * 1024, !isRawData); + [Fact] + public void LargeBuffersAreNotPooled_OfByte() + { + const int mb128 = 128 * 1024 * 1024; + Assert.False(this.CheckIsPooled(2, mb128)); + } + + [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 From b16e002706fa46ba0f6aa9a7e3fd1d024aeddc25 Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Thu, 18 Jan 2018 02:59:46 +0100 Subject: [PATCH 2/2] Use 2 ArrayPool-s with different maxArrayPerBuckets parameters, so we never have more than 8 buckets for large buffers for a single value type. --- src/ImageSharp/Memory/PixelDataPool{T}.cs | 43 ++++++++++++++++--- .../Memory/PixelDataPoolTests.cs | 32 +++++++++----- 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs index f25803951..80c9c410e 100644 --- a/src/ImageSharp/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Memory/PixelDataPool{T}.cs @@ -17,17 +17,32 @@ namespace SixLabors.ImageSharp.Memory /// The maximum size of pooled arrays in bytes. /// Currently set to 32MB, which is equivalent to 8 megapixels of raw data. /// - private const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024; + internal const int MaxPooledBufferSizeInBytes = 32 * 1024 * 1024; /// - /// The maximum array length of the . + /// The threshold to pool arrays in which has less buckets for memory safety. /// - private static readonly int MaxArrayLength = MaxPooledBufferSizeInBytes / Unsafe.SizeOf(); + private const int LargeBufferThresholdInBytes = 8 * 1024 * 1024; /// - /// The which is not kept clean. + /// The maximum array length of the . /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(MaxArrayLength, 50); + 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. @@ -36,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); + } } /// @@ -45,7 +67,14 @@ namespace SixLabors.ImageSharp.Memory /// The array to return to the buffer pool. public static void Return(T[] array) { - ArrayPool.Return(array); + if (array.Length <= MaxNormalArrayLength) + { + NormalArrayPool.Return(array); + } + else + { + LargeArrayPool.Return(array); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs index beaa49dbc..caba9a464 100644 --- a/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Memory/PixelDataPoolTests.cs @@ -9,19 +9,25 @@ using Xunit; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests.Memory { + using System; + /// /// Tests the class. /// public class PixelDataPoolTests { + private const int MaxPooledBufferSizeInBytes = PixelDataPool.MaxPooledBufferSizeInBytes; + readonly object monitor = new object(); - [Fact] - public void PixelDataPoolRentsMinimumSize() + [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] @@ -65,17 +71,21 @@ namespace SixLabors.ImageSharp.Tests.Memory } } - [Fact] - public void SmallBuffersArePooled() + [Theory] + [InlineData(32)] + [InlineData(512)] + [InlineData(MaxPooledBufferSizeInBytes-1)] + public void SmallBuffersArePooled(int size) { - Assert.True(this.CheckIsPooled(5, 512)); + Assert.True(this.CheckIsPooled(5, size)); } - [Fact] - public void LargeBuffersAreNotPooled_OfByte() + [Theory] + [InlineData(128 * 1024 * 1024)] + [InlineData(MaxPooledBufferSizeInBytes+1)] + public void LargeBuffersAreNotPooled_OfByte(int size) { - const int mb128 = 128 * 1024 * 1024; - Assert.False(this.CheckIsPooled(2, mb128)); + Assert.False(this.CheckIsPooled(2, size)); } [StructLayout(LayoutKind.Explicit, Size = 512)]