diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 3a1d667528..aadca236af 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -55,7 +55,9 @@ namespace ImageSharp uint* src = (uint*)sourceColors.PointerAtOffset; uint* srcEnd = src + count; - using (PinnedBuffer tempBuf = new PinnedBuffer(unpackedRawCount + Vector.Count)) + using (PinnedBuffer tempBuf = new PinnedBuffer( + unpackedRawCount + Vector.Count, + PixelDataPool.Dirty)) { uint* tPtr = (uint*)tempBuf.Pointer; uint[] temp = tempBuf.Array; diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index e902388882..8c81a3206d 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -24,22 +24,32 @@ namespace ImageSharp private GCHandle handle; /// - /// A value indicating whether this instance should return the array to the pool. + /// The if the is pooled. /// - private bool isPoolingOwner; + private PixelDataPool pool; /// /// Initializes a new instance of the class. /// /// The desired count of elements. (Minimum size for ) - public PinnedBuffer(int count) + /// The to be used to rent the data. + public PinnedBuffer(int count, PixelDataPool pool) { this.Count = count; - this.Array = PixelDataPool.Rent(count); - this.isPoolingOwner = true; + this.pool = pool; + this.Array = this.pool.Rent(count); this.Pin(); } + /// + /// Initializes a new instance of the class. + /// + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int count) + : this(count, PixelDataPool.Clean) + { + } + /// /// Initializes a new instance of the class. /// @@ -48,6 +58,7 @@ namespace ImageSharp { this.Count = array.Length; this.Array = array; + this.pool = null; this.Pin(); } @@ -65,6 +76,7 @@ namespace ImageSharp this.Count = count; this.Array = array; + this.pool = null; this.Pin(); } @@ -140,11 +152,8 @@ namespace ImageSharp this.IsDisposedOrLostArrayOwnership = true; this.UnPin(); - if (this.isPoolingOwner) - { - PixelDataPool.Return(this.Array); - } - + this.pool?.Return(this.Array); + this.pool = null; this.Array = null; this.Count = 0; diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index a97d17fdbb..a01a941bcd 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -9,39 +9,69 @@ namespace ImageSharp using System.Buffers; /// - /// Provides a resource pool that enables reusing instances of value type arrays . - /// will always return arrays initialized with 'default(T)' + /// Provides a resource pool that enables reusing instances of value type arrays for image data . /// /// The value type. - public static class PixelDataPool + public class PixelDataPool where T : struct { /// - /// The used to pool data. + /// The which will be always cleared. /// - private static readonly ArrayPool ArrayPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + private static readonly ArrayPool CleanPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + + /// + /// The which is not kept clean. + /// + private static readonly ArrayPool DirtyPool = ArrayPool.Create(CalculateMaxArrayLength(), 50); + + /// + /// The backing + /// + private ArrayPool arrayPool; + + /// + /// A value indicating whether clearArray is requested on . + /// + private bool clearArray; + + private PixelDataPool(ArrayPool arrayPool, bool clearArray) + { + this.clearArray = clearArray; + this.arrayPool = arrayPool; + } + + /// + /// Gets the which will always return arrays initialized to default(T) + /// + public static PixelDataPool Clean { get; } = new PixelDataPool(CleanPool, true); + + /// + /// Gets the which does not keep the arrays clean on Rent/Return. + /// + public static PixelDataPool Dirty { get; } = new PixelDataPool(DirtyPool, false); /// /// Rents the pixel array from the pool. /// /// The minimum length of the array to return. /// The - public static T[] Rent(int minimumLength) + public T[] Rent(int minimumLength) { - return ArrayPool.Rent(minimumLength); + return CleanPool.Rent(minimumLength); } /// /// Returns the rented pixel array back to the pool. /// /// The array to return to the buffer pool. - public static void Return(T[] array) + public void Return(T[] array) { - ArrayPool.Return(array, true); + CleanPool.Return(array, this.clearArray); } /// - /// Heuristically calculates a reasonable maxArrayLength value for the backing . + /// Heuristically calculates a reasonable maxArrayLength value for the backing . /// /// The maxArrayLength value internal static int CalculateMaxArrayLength() diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 830318b32b..7d7fc843f0 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -221,7 +221,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); + this.pixelBuffer = PixelDataPool.Clean.Rent(this.Width * this.Height); } /// @@ -229,7 +229,7 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Return(this.pixelBuffer); + PixelDataPool.Clean.Return(this.pixelBuffer); this.pixelBuffer = null; } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs new file mode 100644 index 0000000000..1300913868 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -0,0 +1,61 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public abstract class ToVector4 + where TColor : struct, IPixel + { + private PinnedBuffer source; + + private PinnedBuffer destination; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.source = new PinnedBuffer(this.Count); + this.destination = new PinnedBuffer(this.Count); + } + + [Cleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PerElement() + { + TColor[] s = this.source.Array; + Vector4[] d = this.destination.Array; + + for (int i = 0; i < this.Count; i++) + { + TColor c = s[i]; + d[i] = c.ToVector4(); + } + } + + [Benchmark] + public void CommonBulk() + { + new BulkPixelOperations().ToVector4(this.source, this.destination, this.Count); + } + + [Benchmark] + public void OptimizedBulk() + { + BulkPixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + } + } + + public class ToVector4_Color : ToVector4 + { + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index a182ccd882..0bb0e922c5 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -23,10 +23,10 @@ namespace ImageSharp.Benchmarks.Image private Image bmpDrawing; private CoreImage bmpCore; - [Params(false, true)] + [Params(false)] public bool LargeImage { get; set; } - [Params(false, true)] + [Params(false)] public bool UseOctreeQuantizer { get; set; } [Setup] diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 4d6d159255..c361fd6a1f 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -7,9 +7,9 @@ namespace ImageSharp.Sandbox46 { using System; using System.Runtime.DesignerServices; - - using ImageSharp.Benchmarks.Color.Bulk; + using ImageSharp.Tests; + using ImageSharp.Tests.Colors; using Xunit.Abstractions; @@ -38,21 +38,18 @@ namespace ImageSharp.Sandbox46 public static void Main(string[] args) { // RunDecodeJpegProfilingTests(); - TestPixelAccessorCopyFromXyzw(); + + RunToVector4ProfilingTest(); + Console.ReadLine(); } - private static void TestPixelAccessorCopyFromXyzw() + private static void RunToVector4ProfilingTest() { - PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy(); - benchmark.Width = 64; - benchmark.Setup(); - - benchmark.CopyRawUnsafeInlined(); - - benchmark.Cleanup(); + BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(); + tests.Benchmark_ToVector4(); } - + private static void RunDecodeJpegProfilingTests() { Console.WriteLine("RunDecodeJpegProfilingTests..."); diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 88bc4a9be6..786c77246c 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -1,4 +1,5 @@ -namespace ImageSharp.Tests.Colors +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Colors { using System; using System.Numerics; @@ -30,6 +31,22 @@ (s, d) => ImageSharp.Color.BulkOperations.ToVector4SimdAligned(s, d, 64) ); } + + [Fact] + public void Benchmark_ToVector4() + { + int times = 150000; + int count = 1024; + + using (PinnedBuffer source = new PinnedBuffer(count)) + using (PinnedBuffer dest = new PinnedBuffer(count)) + { + for (int i = 0; i < times; i++) + { + BulkPixelOperations.Instance.ToVector4(source, dest, count); + } + } + } } public class Argb : BulkPixelOperationsTests diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index 001785d60c..db560ba6bb 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -3,6 +3,7 @@ // Licensed under the Apache License, Version 2.0. // +// ReSharper disable InconsistentNaming namespace ImageSharp.Tests { using System.Linq; @@ -14,50 +15,59 @@ namespace ImageSharp.Tests /// public class PixelDataPoolTests { - [Fact] - public void PixelDataPoolRentsMinimumSize() + private static PixelDataPool GetPool(bool clean) { - Color[] pixels = PixelDataPool.Rent(1024); + return clean ? PixelDataPool.Clean : PixelDataPool.Dirty; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void PixelDataPoolRentsMinimumSize(bool clean) + { + Color[] pixels = GetPool(clean).Rent(1024); Assert.True(pixels.Length >= 1024); } [Fact] - public void PixelDataPoolRentsEmptyArray() + public void PixelDataPool_Clean_RentsCleanArray() { for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelDataPool.Rent(i); + Color[] pixels = PixelDataPool.Clean.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelDataPool.Return(pixels); + PixelDataPool.Clean.Return(pixels); } for (int i = 16; i < 1024; i += 16) { - Color[] pixels = PixelDataPool.Rent(i); + Color[] pixels = PixelDataPool.Clean.Rent(i); Assert.True(pixels.All(p => p == default(Color))); - PixelDataPool.Return(pixels); + PixelDataPool.Clean.Return(pixels); } } - [Fact] - public void PixelDataPoolDoesNotThrowWhenReturningNonPooled() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void PixelDataPoolDoesNotThrowWhenReturningNonPooled(bool clean) { Color[] pixels = new Color[1024]; - PixelDataPool.Return(pixels); + GetPool(clean).Return(pixels); Assert.True(pixels.Length >= 1024); } [Fact] - public void PixelDataPoolCleansRentedArray() + public void PixelDataPool_Clean_CleansRentedArray() { - Color[] pixels = PixelDataPool.Rent(256); + Color[] pixels = PixelDataPool.Clean.Rent(256); for (int i = 0; i < pixels.Length; i++) { @@ -66,7 +76,7 @@ namespace ImageSharp.Tests Assert.True(pixels.All(p => p == Color.Azure)); - PixelDataPool.Return(pixels); + PixelDataPool.Clean.Return(pixels); Assert.True(pixels.All(p => p == default(Color))); } @@ -85,7 +95,7 @@ namespace ImageSharp.Tests [Fact] public void RentNonIPixelData() { - byte[] data = PixelDataPool.Rent(16384); + byte[] data = PixelDataPool.Clean.Rent(16384); Assert.True(data.Length >= 16384); }