From bcf58cd5a2ef20c4fe8db24769e4d11632d4b3be Mon Sep 17 00:00:00 2001 From: Anton Firszov Date: Sat, 13 May 2017 02:16:58 +0200 Subject: [PATCH] Optimized Rgba32 constructors. Created PixelConversion benchmarks. --- src/ImageSharp/PixelFormats/Argb32.cs | 15 +- src/ImageSharp/PixelFormats/Rgba32.cs | 42 ++++- .../General/PixelConversion.cs | 156 ++++++++++++++++++ 3 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 tests/ImageSharp.Benchmarks/General/PixelConversion.cs diff --git a/src/ImageSharp/PixelFormats/Argb32.cs b/src/ImageSharp/PixelFormats/Argb32.cs index 61e860aee..cb2a1b87e 100644 --- a/src/ImageSharp/PixelFormats/Argb32.cs +++ b/src/ImageSharp/PixelFormats/Argb32.cs @@ -58,11 +58,24 @@ namespace ImageSharp.PixelFormats /// The green component. /// The blue component. /// The alpha component. - public Argb32(byte r, byte g, byte b, byte a = 255) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Argb32(byte r, byte g, byte b, byte a) { this.PackedValue = Pack(r, g, b, a); } + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Argb32(byte r, byte g, byte b) + { + this.PackedValue = Pack(r, g, b, 255); + } + /// /// Initializes a new instance of the struct. /// diff --git a/src/ImageSharp/PixelFormats/Rgba32.cs b/src/ImageSharp/PixelFormats/Rgba32.cs index 15a9ed0fd..c5f752336 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.cs @@ -49,12 +49,6 @@ namespace ImageSharp [FieldOffset(3)] public byte A; - /// - /// The packed representation of the value. - /// - [FieldOffset(0)] - public uint Rgba; - /// /// The shift count for the red component /// @@ -85,6 +79,21 @@ namespace ImageSharp /// private static readonly Vector4 Half = new Vector4(0.5F); + /// + /// Initializes a new instance of the struct. + /// + /// The red component. + /// The green component. + /// The blue component. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32(byte r, byte g, byte b) + { + this.R = r; + this.G = g; + this.B = b; + this.A = 255; + } + /// /// Initializes a new instance of the struct. /// @@ -93,8 +102,7 @@ namespace ImageSharp /// The blue component. /// The alpha component. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Rgba32(byte r, byte g, byte b, byte a = 255) - : this() + public Rgba32(byte r, byte g, byte b, byte a) { this.R = r; this.G = g; @@ -155,6 +163,24 @@ namespace ImageSharp this.Rgba = packed; } + /// + /// Gets or sets the packed representation of the Rgba32 struct. + /// + public uint Rgba + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return Unsafe.As(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + Unsafe.As(ref this) = value; + } + } + /// public uint PackedValue { get => this.Rgba; set => this.Rgba = value; } diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion.cs new file mode 100644 index 000000000..77e728280 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/PixelConversion.cs @@ -0,0 +1,156 @@ +namespace ImageSharp.Benchmarks.General +{ + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + using BenchmarkDotNet.Attributes; + + /// + /// When implementing TPixel --> Rgba32 style conversions on IPixel, should which API should we prefer? + /// 1. Rgba32 ToRgba32(); + /// OR + /// 2. void CopyToRgba32(ref Rgba32 dest); + /// ? + /// + public class PixelConversion + { + interface ITestPixel + where T : struct, ITestPixel + { + Rgba32 ToRgba32(); + + void CopyToRgba32(ref Rgba32 dest); + } + + [StructLayout(LayoutKind.Sequential)] + struct TestArgb : ITestPixel + { + private byte a, r, g, b; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ToRgba32() + { + return new Rgba32(this.r, this.g, this.b, this.a); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToRgba32(ref Rgba32 dest) + { + dest.R = this.r; + dest.G = this.g; + dest.B = this.b; + dest.A = this.a; + } + } + + [StructLayout(LayoutKind.Sequential)] + struct TestRgba : ITestPixel + { + private byte r, g, b, a; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Rgba32 ToRgba32() + { + return Unsafe.As(ref this); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyToRgba32(ref Rgba32 dest) + { + dest = Unsafe.As(ref this); + } + } + + struct ConversionRunner + where T : struct, ITestPixel + { + private T[] source; + + private Rgba32[] dest; + + public ConversionRunner(int count) + { + this.source = new T[count]; + this.dest = new Rgba32[count]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunRetvalConversion() + { + int count = this.source.Length; + + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; + + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref destBaseRef, i) = Unsafe.Add(ref sourceBaseRef, i).ToRgba32(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RunCopyToConversion() + { + int count = this.source.Length; + + ref T sourceBaseRef = ref this.source[0]; + ref Rgba32 destBaseRef = ref this.dest[0]; + + for (int i = 0; i < count; i++) + { + Unsafe.Add(ref sourceBaseRef, i).CopyToRgba32(ref Unsafe.Add(ref destBaseRef, i)); + } + } + } + + private ConversionRunner inOrderRunner; + + private ConversionRunner permutedRunner; + + [Params(128)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.inOrderRunner = new ConversionRunner(this.Count); + this.permutedRunner = new ConversionRunner(this.Count); + } + + [Benchmark(Baseline = true)] + public void InOrderRetval() + { + this.inOrderRunner.RunRetvalConversion(); + } + + [Benchmark] + public void InOrderCopyTo() + { + this.inOrderRunner.RunCopyToConversion(); + } + + [Benchmark] + public void PermutedRetval() + { + this.permutedRunner.RunRetvalConversion(); + } + + [Benchmark] + public void PermutedCopyTo() + { + this.permutedRunner.RunCopyToConversion(); + } + } + + /* + * Results: + * + * Method | Count | Mean | StdDev | Scaled | Scaled-StdDev | + * --------------- |------ |------------ |---------- |------- |-------------- | + * InOrderRetval | 128 | 89.7358 ns | 2.2389 ns | 1.00 | 0.00 | + * InOrderCopyTo | 128 | 89.4112 ns | 2.2901 ns | 1.00 | 0.03 | + * PermutedRetval | 128 | 845.4038 ns | 5.6154 ns | 9.43 | 0.23 | + * PermutedCopyTo | 128 | 155.6004 ns | 3.8870 ns | 1.73 | 0.06 | + * + */ +} \ No newline at end of file