From 33627ee257d4ff5581870b82b73e6d0590702e1e Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Mon, 20 Feb 2017 01:17:31 +0100 Subject: [PATCH] Benchmarks: PixelAccessorVirtualCopy, vectorization --- src/ImageSharp/Image/PixelAccessor{TColor}.cs | 5 + .../Color/Bulk/PixelAccessorVirtualCopy.cs | 129 ++++++++++++++++++ .../Drawing/DrawBeziers.cs | 2 +- .../Drawing/DrawLines.cs | 2 +- .../Drawing/DrawPolygon.cs | 2 +- .../Drawing/FillWithPattern.cs | 2 +- .../General/Vectorization/BitwiseOrUint32.cs | 54 ++++++++ .../General/Vectorization/DivFloat.cs | 54 ++++++++ .../General/Vectorization/DivUInt32.cs | 54 ++++++++ .../General/Vectorization/MulFloat.cs | 54 ++++++++ .../General/Vectorization/MulUInt32.cs | 54 ++++++++ .../Vectorization/ReinterpretUInt32AsFloat.cs | 62 +++++++++ .../ImageSharp.Sandbox46.csproj | 11 +- tests/ImageSharp.Sandbox46/Program.cs | 19 ++- tests/ImageSharp.Sandbox46/app.config | 4 + tests/ImageSharp.Sandbox46/packages.config | 1 + 16 files changed, 501 insertions(+), 8 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs create mode 100644 tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index 338f49182..b31ada10b 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -120,6 +120,11 @@ namespace ImageSharp /// public bool PooledMemory { get; private set; } + /// + /// Gets the pixel buffer array. + /// + public TColor[] PixelBuffer => this.pixelBuffer; + /// /// Gets the pointer to the pixel buffer. /// diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs new file mode 100644 index 000000000..ed649792f --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PixelAccessorVirtualCopy.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ImageSharp.Benchmarks.Color.Bulk +{ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + using Color = ImageSharp.Color; + + /// + /// Benchmark to measure the effect of using virtual bulk-copy calls inside PixelAccessor methods + /// + public unsafe class PixelAccessorVirtualCopy + { + abstract class CopyExecutor + { + internal abstract void VirtualCopy(ArrayPointer destination, ArrayPointer source, int count); + } + + class UnsafeCopyExecutor : CopyExecutor + { + [MethodImpl(MethodImplOptions.NoInlining)] + internal override unsafe void VirtualCopy(ArrayPointer destination, ArrayPointer source, int count) + { + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, (uint)count*4); + } + } + + private PixelAccessor pixelAccessor; + + private PixelArea area; + + private CopyExecutor executor; + + [Params(64, 256)] + public int Width { get; set; } + + public int Height { get; set; } = 256; + + + [Setup] + public void Setup() + { + this.pixelAccessor = new PixelAccessor(this.Width, this.Height); + this.area = new PixelArea(this.Width / 2, this.Height, ComponentOrder.Xyzw); + this.executor = new UnsafeCopyExecutor(); + } + + [Cleanup] + public void Cleanup() + { + this.pixelAccessor.Dispose(); + this.area.Dispose(); + } + + [Benchmark(Baseline = true)] + public void CopyRawUnsafeInlined() + { + uint byteCount = (uint)this.area.Width * 4; + + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + byte* source = this.area.PixelBase + (y * this.area.RowStride); + byte* destination = this.GetRowPointer(targetX, targetY + y); + + Unsafe.CopyBlock(destination, source, byteCount); + } + } + + [Benchmark] + public void CopyArrayPointerUnsafeInlined() + { + uint byteCount = (uint)this.area.Width * 4; + + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + ArrayPointer source = this.GetAreaRow(y); + ArrayPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + Unsafe.CopyBlock((void*)destination.PointerAtOffset, (void*)source.PointerAtOffset, byteCount); + } + } + + [Benchmark] + public void CopyArrayPointerUnsafeVirtual() + { + int targetX = this.Width / 4; + int targetY = 0; + + for (int y = 0; y < this.Height; y++) + { + ArrayPointer source = this.GetAreaRow(y); + ArrayPointer destination = this.GetPixelAccessorRow(targetX, targetY + y); + this.executor.VirtualCopy(destination, source, this.area.Width); + } + } + + private byte* GetRowPointer(int x, int y) + { + return (byte*)this.pixelAccessor.DataPointer + (((y * this.pixelAccessor.Width) + x) * Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ArrayPointer GetPixelAccessorRow(int x, int y) + { + return new ArrayPointer( + this.pixelAccessor.PixelBuffer, + (void*)this.pixelAccessor.DataPointer, + (y * this.pixelAccessor.Width) + x + ); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ArrayPointer GetAreaRow(int y) + { + return new ArrayPointer(this.area.Bytes, this.area.PixelBase, y * this.area.RowStride); + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs index c066ac18c..a10417b90 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawBeziers(pen, new[] { new PointF(10, 500), new PointF(30, 10), diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs index 78f71b660..146def363 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawLines(pen, new[] { new PointF(10, 10), new PointF(550, 50), diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs index 88618b912..e6c1ac0d6 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(Color.HotPink, 10); + var pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawPolygon(pen, new[] { new PointF(10, 10), new PointF(550, 50), diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs index 718474f1f..589ac0cd4 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks using (Graphics graphics = Graphics.FromImage(destination)) { graphics.SmoothingMode = SmoothingMode.AntiAlias; - var brush = new HatchBrush(HatchStyle.BackwardDiagonal, Color.HotPink); + var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink); graphics.FillRectangle(brush, new Rectangle(0,0, 800,800)); // can't find a way to flood fill with a brush } using (MemoryStream ms = new MemoryStream()) diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs new file mode 100644 index 000000000..dd20e85d5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/BitwiseOrUint32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class BitwiseOrUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint) i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] | v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i+=Vector.Count) + { + Vector a = new Vector(this.input, i); + a = Vector.BitwiseOr(a, v); + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs new file mode 100644 index 000000000..61582b7dc --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivFloat.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivFloat + { + private float[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + private float testValue; + + [Setup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs new file mode 100644 index 000000000..75fa03c04 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/DivUInt32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class DivUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] / v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a / v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs new file mode 100644 index 000000000..151145e12 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulFloat.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class MulFloat + { + private float[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + private float testValue; + + [Setup] + public void Setup() + { + this.input = new float[this.InputSize]; + this.result = new float[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + float v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs new file mode 100644 index 000000000..f7d6cf9b9 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/MulUInt32.cs @@ -0,0 +1,54 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + + using BenchmarkDotNet.Attributes; + + public class MulUInt32 + { + private uint[] input; + + private uint[] result; + + [Params(32)] + public int InputSize { get; set; } + + private uint testValue; + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new uint[this.InputSize]; + this.testValue = 42; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + uint v = this.testValue; + for (int i = 0; i < this.input.Length; i++) + { + this.result[i] = this.input[i] * v; + } + } + + [Benchmark] + public void Simd() + { + Vector v = new Vector(this.testValue); + + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + a = a * v; + a.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs new file mode 100644 index 000000000..b0ca181cd --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/Vectorization/ReinterpretUInt32AsFloat.cs @@ -0,0 +1,62 @@ +namespace ImageSharp.Benchmarks.General.Vectorization +{ + using System.Numerics; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + public class ReinterpretUInt32AsFloat + { + private uint[] input; + + private float[] result; + + [Params(32)] + public int InputSize { get; set; } + + [StructLayout(LayoutKind.Explicit)] + struct UIntFloatUnion + { + [FieldOffset(0)] + public float f; + + [FieldOffset(0)] + public uint i; + } + + + [Setup] + public void Setup() + { + this.input = new uint[this.InputSize]; + this.result = new float[this.InputSize]; + + for (int i = 0; i < this.InputSize; i++) + { + this.input[i] = (uint)i; + } + } + + [Benchmark(Baseline = true)] + public void Standard() + { + UIntFloatUnion u = default(UIntFloatUnion); + for (int i = 0; i < this.input.Length; i++) + { + u.i = this.input[i]; + this.result[i] = u.f; + } + } + + [Benchmark] + public void Simd() + { + for (int i = 0; i < this.input.Length; i += Vector.Count) + { + Vector a = new Vector(this.input, i); + Vector b = Vector.AsVectorSingle(a); + b.CopyTo(this.result, i); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj index 2444db031..d1b059f44 100644 --- a/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj +++ b/tests/ImageSharp.Sandbox46/ImageSharp.Sandbox46.csproj @@ -109,6 +109,10 @@ ..\..\packages\System.Reflection.Metadata.1.3.0\lib\portable-net45+win8\System.Reflection.Metadata.dll True + + ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.3.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + True + ..\..\packages\System.Security.Cryptography.Algorithms.4.2.0\lib\net461\System.Security.Cryptography.Algorithms.dll True @@ -202,6 +206,9 @@ + + Benchmarks\PixelAccessorVirtualCopy.cs + Tests\Drawing\PolygonTests.cs @@ -327,9 +334,7 @@ - - - + diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 48219902b..f289ac2db 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Sandbox46 using System; using System.Runtime.DesignerServices; + using ImageSharp.Benchmarks.Color.Bulk; using ImageSharp.Tests; using Xunit.Abstractions; @@ -36,7 +37,23 @@ namespace ImageSharp.Sandbox46 /// public static void Main(string[] args) { - RunDecodeJpegProfilingTests(); + //RunDecodeJpegProfilingTests(); + TestPixelAccessorCopyFromXyzw(); + Console.ReadLine(); + } + + private static void TestPixelAccessorCopyFromXyzw() + { + PixelAccessorVirtualCopy benchmark = new PixelAccessorVirtualCopy(); + benchmark.Width = 64; + benchmark.Setup(); + + benchmark.CopyRawUnsafeInlined(); + benchmark.CopyArrayPointerUnsafe(); + benchmark.CopyArrayPointerVirtualUnsafe(); + benchmark.CopyArrayPointerVirtualMarshal(); + + benchmark.Cleanup(); } private static void RunDecodeJpegProfilingTests() diff --git a/tests/ImageSharp.Sandbox46/app.config b/tests/ImageSharp.Sandbox46/app.config index 5a049c66e..3328297a5 100644 --- a/tests/ImageSharp.Sandbox46/app.config +++ b/tests/ImageSharp.Sandbox46/app.config @@ -10,6 +10,10 @@ + + + + \ No newline at end of file diff --git a/tests/ImageSharp.Sandbox46/packages.config b/tests/ImageSharp.Sandbox46/packages.config index 65ad74fa6..426f5f1b5 100644 --- a/tests/ImageSharp.Sandbox46/packages.config +++ b/tests/ImageSharp.Sandbox46/packages.config @@ -29,6 +29,7 @@ +