From f091aadd9231e288337465e499eab4cd7098607d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Tue, 20 Aug 2024 21:21:38 +0200 Subject: [PATCH] Improved performance of MinMax on Images --- HPPH.Benchmark/MinMaxBenchmarks.cs | 25 ++++ HPPH.Generators/Features/MinMax.cs | 7 +- .../ColorFormatABGR.MinMax.g.cs | 4 +- .../ColorFormatARGB.MinMax.g.cs | 4 +- .../ColorFormatBGR.MinMax.g.cs | 4 +- .../ColorFormatBGRA.MinMax.g.cs | 4 +- .../ColorFormatRGB.MinMax.g.cs | 4 +- .../ColorFormatRGBA.MinMax.g.cs | 4 +- .../IColorFormat.MinMax.g.cs | 3 +- HPPH/PixelHelper.MinMax.cs | 140 +++++++++++------- 10 files changed, 134 insertions(+), 65 deletions(-) diff --git a/HPPH.Benchmark/MinMaxBenchmarks.cs b/HPPH.Benchmark/MinMaxBenchmarks.cs index 1774677..41f3d49 100644 --- a/HPPH.Benchmark/MinMaxBenchmarks.cs +++ b/HPPH.Benchmark/MinMaxBenchmarks.cs @@ -13,6 +13,8 @@ public class MinMaxBenchmarks private readonly List _colors3bpp; private readonly List _colors4bpp; + private readonly List> _images3bpp; + private readonly List> _images4bpp; #endregion @@ -22,6 +24,9 @@ public class MinMaxBenchmarks { _colors3bpp = BenchmarkHelper.GetSampleData(); _colors4bpp = BenchmarkHelper.GetSampleData(); + + _images3bpp = BenchmarkHelper.GetSampleDataImages(); + _images4bpp = BenchmarkHelper.GetSampleDataImages(); } #endregion @@ -48,6 +53,26 @@ public class MinMaxBenchmarks return minMax; } + [Benchmark] + public IMinMax[] PixelHelper_3BPP_Image() + { + IMinMax[] minMax = new IMinMax[_images3bpp.Count]; + for (int i = 0; i < _images3bpp.Count; i++) + minMax[i] = _images3bpp[i].MinMax(); + + return minMax; + } + + [Benchmark] + public IMinMax[] PixelHelper_4BPP_Image() + { + IMinMax[] minMax = new IMinMax[_images4bpp.Count]; + for (int i = 0; i < _images4bpp.Count; i++) + minMax[i] = _images4bpp[i].MinMax(); + + return minMax; + } + [Benchmark] public IMinMax[] Reference_3BPP() { diff --git a/HPPH.Generators/Features/MinMax.cs b/HPPH.Generators/Features/MinMax.cs index cc1639c..cffaf42 100644 --- a/HPPH.Generators/Features/MinMax.cs +++ b/HPPH.Generators/Features/MinMax.cs @@ -139,6 +139,7 @@ internal class MinMax : IGeneratorFeature return $$""" #nullable enable + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -147,7 +148,8 @@ internal class MinMax : IGeneratorFeature { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => {{(colorFormat.Bpp == 3 ? $"Unsafe.BitCast(data);" : "throw new NotSupportedException();")}} + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => {{(colorFormat.Bpp == 4 ? $"Unsafe.BitCast(data);" : "throw new NotSupportedException();")}} #endregion } @@ -163,7 +165,8 @@ internal class MinMax : IGeneratorFeature public partial interface IColorFormat { - internal IMinMax MinMax(ReadOnlySpan data); + internal IMinMax ToMinMax(Generic3ByteMinMax data); + internal IMinMax ToMinMax(Generic4ByteMinMax data); } """; } diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs index 7d073ea..666c565 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.MinMax.g.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -8,7 +9,8 @@ public sealed partial class ColorFormatABGR { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs index ca9d201..361786f 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.MinMax.g.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -8,7 +9,8 @@ public sealed partial class ColorFormatARGB { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs index a2857c1..f796204 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.MinMax.g.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGR { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast(data); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException(); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs index 89a0845..9156e11 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.MinMax.g.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGRA { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs index ab55ee1..9df69f0 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.MinMax.g.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGB { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast(data); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException(); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs index 7e07522..2134c56 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.MinMax.g.cs @@ -1,5 +1,6 @@ #nullable enable +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGBA { #region Methods - unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan data) => PixelHelper.MinMax(MemoryMarshal.Cast(data)); + unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException(); + unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs index 35116a0..8b9412e 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.MinMax.g.cs @@ -4,5 +4,6 @@ namespace HPPH; public partial interface IColorFormat { - internal IMinMax MinMax(ReadOnlySpan data); + internal IMinMax ToMinMax(Generic3ByteMinMax data); + internal IMinMax ToMinMax(Generic4ByteMinMax data); } \ No newline at end of file diff --git a/HPPH/PixelHelper.MinMax.cs b/HPPH/PixelHelper.MinMax.cs index 86cd3ec..d644584 100644 --- a/HPPH/PixelHelper.MinMax.cs +++ b/HPPH/PixelHelper.MinMax.cs @@ -1,5 +1,4 @@ -using System.Buffers; -using System.Numerics; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -13,29 +12,40 @@ public static unsafe partial class PixelHelper { ArgumentNullException.ThrowIfNull(image); - int dataLength = image.SizeInBytes; + IColorFormat colorFormat = image.ColorFormat; - if (dataLength <= 1024) + if (colorFormat.BytesPerPixel == 3) { - Span buffer = stackalloc byte[dataLength]; + if (image.Height == 0) return colorFormat.ToMinMax(new Generic3ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue)); + if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(image.Rows[0].AsByteSpan()))); - image.CopyTo(buffer); - return image.ColorFormat.MinMax(buffer); + Generic3ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + for (int y = 0; y < image.Height; y++) + result = MinMax(MemoryMarshal.Cast(image.Rows[y].AsByteSpan()), + result.B1Min, result.B1Max, + result.B2Min, result.B2Max, + result.B3Min, result.B3Max); + + return colorFormat.ToMinMax(result); } - else + + if (colorFormat.BytesPerPixel == 4) { - byte[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..dataLength]; - try - { - image.CopyTo(buffer); - return image.ColorFormat.MinMax(buffer); - } - finally - { - ArrayPool.Shared.Return(array); - } + if (image.Height == 0) return colorFormat.ToMinMax(new Generic4ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue)); + if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(image.Rows[0].AsByteSpan()))); + + Generic4ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + for (int y = 0; y < image.Height; y++) + result = MinMax(MemoryMarshal.Cast(image.Rows[y].AsByteSpan()), + result.B1Min, result.B1Max, + result.B2Min, result.B2Max, + result.B3Min, result.B3Max, + result.B4Min, result.B4Max); + + return colorFormat.ToMinMax(result); } + + throw new NotSupportedException("Data is not of a supported valid color-type."); } public static IMinMax MinMax(this IImage image) @@ -49,58 +59,75 @@ public static unsafe partial class PixelHelper public static IMinMax MinMax(this RefImage image) where T : struct, IColor { - int dataLength = image.Width * image.Height; - int sizeInBytes = dataLength * T.ColorFormat.BytesPerPixel; + IColorFormat colorFormat = T.ColorFormat; - if (sizeInBytes <= 1024) + if (colorFormat.BytesPerPixel == 3) { - Span buffer = MemoryMarshal.Cast(stackalloc byte[sizeInBytes]); + if (image.Height == 0) return colorFormat.ToMinMax(new Generic3ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue)); + if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(image.Rows[0].AsByteSpan()))); - image.CopyTo(buffer); - return MinMax(buffer); + Generic3ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + for (int y = 0; y < image.Height; y++) + result = MinMax(MemoryMarshal.Cast(image.Rows[y].AsByteSpan()), + result.B1Min, result.B1Max, + result.B2Min, result.B2Max, + result.B3Min, result.B3Max); + + return colorFormat.ToMinMax(result); } - else + + if (colorFormat.BytesPerPixel == 4) { - T[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..(dataLength)]; - try - { - image.CopyTo(buffer); - return MinMax(buffer); - } - finally - { - ArrayPool.Shared.Return(array); - } + if (image.Height == 0) return colorFormat.ToMinMax(new Generic4ByteMinMax(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue)); + if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(image.Rows[0].AsByteSpan()))); + + Generic4ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + for (int y = 0; y < image.Height; y++) + result = MinMax(MemoryMarshal.Cast(image.Rows[y].AsByteSpan()), + result.B1Min, result.B1Max, + result.B2Min, result.B2Max, + result.B3Min, result.B3Max, + result.B4Min, result.B4Max); + + return colorFormat.ToMinMax(result); } + + throw new NotSupportedException("Data is not of a supported valid color-type."); } + public static IMinMax MinMax(this Span colors) where T : struct, IColor - => T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors)); - - public static IMinMax MinMax(this ReadOnlySpan colors) - where T : struct, IColor - => T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors)); - - internal static IMinMax MinMax(ReadOnlySpan colors) - where T : struct, IColor - where TMinMax : struct, IMinMax { if (colors == null) throw new ArgumentNullException(nameof(colors)); - return T.ColorFormat.BytesPerPixel switch + IColorFormat colorFormat = T.ColorFormat; + return colorFormat.BytesPerPixel switch { - 3 => Unsafe.BitCast(MinMax(MemoryMarshal.Cast(colors))), - 4 => Unsafe.BitCast(MinMax(MemoryMarshal.Cast(colors))), + 3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), + 4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), _ => throw new NotSupportedException("Data is not of a supported valid color-type.") }; } - private static Generic3ByteMinMax MinMax(ReadOnlySpan data) + public static IMinMax MinMax(this ReadOnlySpan colors) + where T : struct, IColor { - byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue; - byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue; + if (colors == null) throw new ArgumentNullException(nameof(colors)); + IColorFormat colorFormat = T.ColorFormat; + return colorFormat.BytesPerPixel switch + { + 3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), + 4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast(colors))), + _ => throw new NotSupportedException("Data is not of a supported valid color-type.") + }; + } + + private static Generic3ByteMinMax MinMax(ReadOnlySpan data) => MinMax(data, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Generic3ByteMinMax MinMax(ReadOnlySpan data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3) + { const int BYTES_PER_COLOR = 3; int elementsPerVector = Vector.Count / BYTES_PER_COLOR; @@ -166,11 +193,12 @@ public static unsafe partial class PixelHelper return new Generic3ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3); } - private static Generic4ByteMinMax MinMax(ReadOnlySpan data) - { - byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue, minB4 = byte.MaxValue; - byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue, maxB4 = byte.MinValue; + private static Generic4ByteMinMax MinMax(ReadOnlySpan data) => MinMax(data, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Generic4ByteMinMax MinMax(ReadOnlySpan data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3, byte minB4, byte maxB4) + { const int BYTES_PER_COLOR = 4; int elementsPerVector = Vector.Count / BYTES_PER_COLOR;