Improved performance of MinMax on Images

This commit is contained in:
Darth Affe 2024-08-20 21:21:38 +02:00
parent e6b77ecfdb
commit f091aadd92
10 changed files with 134 additions and 65 deletions

View File

@ -13,6 +13,8 @@ public class MinMaxBenchmarks
private readonly List<ColorRGB[]> _colors3bpp; private readonly List<ColorRGB[]> _colors3bpp;
private readonly List<ColorRGBA[]> _colors4bpp; private readonly List<ColorRGBA[]> _colors4bpp;
private readonly List<IImage<ColorRGB>> _images3bpp;
private readonly List<IImage<ColorRGBA>> _images4bpp;
#endregion #endregion
@ -22,6 +24,9 @@ public class MinMaxBenchmarks
{ {
_colors3bpp = BenchmarkHelper.GetSampleData<ColorRGB>(); _colors3bpp = BenchmarkHelper.GetSampleData<ColorRGB>();
_colors4bpp = BenchmarkHelper.GetSampleData<ColorRGBA>(); _colors4bpp = BenchmarkHelper.GetSampleData<ColorRGBA>();
_images3bpp = BenchmarkHelper.GetSampleDataImages<ColorRGB>();
_images4bpp = BenchmarkHelper.GetSampleDataImages<ColorRGBA>();
} }
#endregion #endregion
@ -48,6 +53,26 @@ public class MinMaxBenchmarks
return minMax; 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] [Benchmark]
public IMinMax[] Reference_3BPP() public IMinMax[] Reference_3BPP()
{ {

View File

@ -139,6 +139,7 @@ internal class MinMax : IGeneratorFeature
return $$""" return $$"""
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -147,7 +148,8 @@ internal class MinMax : IGeneratorFeature
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<Color{{colorFormat.Format}}, MinMax{{colorFormat.Format}}>(MemoryMarshal.Cast<byte, Color{{colorFormat.Format}}>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => {{(colorFormat.Bpp == 3 ? $"Unsafe.BitCast<Generic3ByteMinMax, MinMax{colorFormat.Format}>(data);" : "throw new NotSupportedException();")}}
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => {{(colorFormat.Bpp == 4 ? $"Unsafe.BitCast<Generic4ByteMinMax, MinMax{colorFormat.Format}>(data);" : "throw new NotSupportedException();")}}
#endregion #endregion
} }
@ -163,7 +165,8 @@ internal class MinMax : IGeneratorFeature
public partial interface IColorFormat public partial interface IColorFormat
{ {
internal IMinMax MinMax(ReadOnlySpan<byte> data); internal IMinMax ToMinMax(Generic3ByteMinMax data);
internal IMinMax ToMinMax(Generic4ByteMinMax data);
} }
"""; """;
} }

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -8,7 +9,8 @@ public sealed partial class ColorFormatABGR
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorABGR, MinMaxABGR>(MemoryMarshal.Cast<byte, ColorABGR>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxABGR>(data);
#endregion #endregion
} }

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -8,7 +9,8 @@ public sealed partial class ColorFormatARGB
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorARGB, MinMaxARGB>(MemoryMarshal.Cast<byte, ColorARGB>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxARGB>(data);
#endregion #endregion
} }

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGR
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorBGR, MinMaxBGR>(MemoryMarshal.Cast<byte, ColorBGR>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast<Generic3ByteMinMax, MinMaxBGR>(data);
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException();
#endregion #endregion
} }

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -8,7 +9,8 @@ public sealed partial class ColorFormatBGRA
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorBGRA, MinMaxBGRA>(MemoryMarshal.Cast<byte, ColorBGRA>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxBGRA>(data);
#endregion #endregion
} }

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGB
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorRGB, MinMaxRGB>(MemoryMarshal.Cast<byte, ColorRGB>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => Unsafe.BitCast<Generic3ByteMinMax, MinMaxRGB>(data);
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => throw new NotSupportedException();
#endregion #endregion
} }

View File

@ -1,5 +1,6 @@
#nullable enable #nullable enable
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace HPPH; namespace HPPH;
@ -8,7 +9,8 @@ public sealed partial class ColorFormatRGBA
{ {
#region Methods #region Methods
unsafe IMinMax IColorFormat.MinMax(ReadOnlySpan<byte> data) => PixelHelper.MinMax<ColorRGBA, MinMaxRGBA>(MemoryMarshal.Cast<byte, ColorRGBA>(data)); unsafe IMinMax IColorFormat.ToMinMax(Generic3ByteMinMax data) => throw new NotSupportedException();
unsafe IMinMax IColorFormat.ToMinMax(Generic4ByteMinMax data) => Unsafe.BitCast<Generic4ByteMinMax, MinMaxRGBA>(data);
#endregion #endregion
} }

View File

@ -4,5 +4,6 @@ namespace HPPH;
public partial interface IColorFormat public partial interface IColorFormat
{ {
internal IMinMax MinMax(ReadOnlySpan<byte> data); internal IMinMax ToMinMax(Generic3ByteMinMax data);
internal IMinMax ToMinMax(Generic4ByteMinMax data);
} }

View File

@ -1,5 +1,4 @@
using System.Buffers; using System.Numerics;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -13,29 +12,40 @@ public static unsafe partial class PixelHelper
{ {
ArgumentNullException.ThrowIfNull(image); ArgumentNullException.ThrowIfNull(image);
int dataLength = image.SizeInBytes; IColorFormat colorFormat = image.ColorFormat;
if (dataLength <= 1024) if (colorFormat.BytesPerPixel == 3)
{ {
Span<byte> 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<byte, Generic3ByteData>(image.Rows[0].AsByteSpan())));
image.CopyTo(buffer); Generic3ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
return image.ColorFormat.MinMax(buffer); for (int y = 0; y < image.Height; y++)
result = MinMax(MemoryMarshal.Cast<byte, Generic3ByteData>(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<byte>.Shared.Rent(dataLength); 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));
Span<byte> buffer = array.AsSpan()[..dataLength]; if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[0].AsByteSpan())));
try
{ Generic4ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
image.CopyTo(buffer); for (int y = 0; y < image.Height; y++)
return image.ColorFormat.MinMax(buffer); result = MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[y].AsByteSpan()),
} result.B1Min, result.B1Max,
finally result.B2Min, result.B2Max,
{ result.B3Min, result.B3Max,
ArrayPool<byte>.Shared.Return(array); result.B4Min, result.B4Max);
}
return colorFormat.ToMinMax(result);
} }
throw new NotSupportedException("Data is not of a supported valid color-type.");
} }
public static IMinMax MinMax<T>(this IImage<T> image) public static IMinMax MinMax<T>(this IImage<T> image)
@ -49,58 +59,75 @@ public static unsafe partial class PixelHelper
public static IMinMax MinMax<T>(this RefImage<T> image) public static IMinMax MinMax<T>(this RefImage<T> image)
where T : struct, IColor where T : struct, IColor
{ {
int dataLength = image.Width * image.Height; IColorFormat colorFormat = T.ColorFormat;
int sizeInBytes = dataLength * T.ColorFormat.BytesPerPixel;
if (sizeInBytes <= 1024) if (colorFormat.BytesPerPixel == 3)
{ {
Span<T> buffer = MemoryMarshal.Cast<byte, T>(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<byte, Generic3ByteData>(image.Rows[0].AsByteSpan())));
image.CopyTo(buffer); Generic3ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
return MinMax(buffer); for (int y = 0; y < image.Height; y++)
result = MinMax(MemoryMarshal.Cast<byte, Generic3ByteData>(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<T>.Shared.Rent(dataLength); 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));
Span<T> buffer = array.AsSpan()[..(dataLength)]; if (image.Height == 1) return colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[0].AsByteSpan())));
try
{ Generic4ByteMinMax result = new(byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
image.CopyTo(buffer); for (int y = 0; y < image.Height; y++)
return MinMax(buffer); result = MinMax(MemoryMarshal.Cast<byte, Generic4ByteData>(image.Rows[y].AsByteSpan()),
} result.B1Min, result.B1Max,
finally result.B2Min, result.B2Max,
{ result.B3Min, result.B3Max,
ArrayPool<T>.Shared.Return(array); result.B4Min, result.B4Max);
}
return colorFormat.ToMinMax(result);
} }
throw new NotSupportedException("Data is not of a supported valid color-type.");
} }
public static IMinMax MinMax<T>(this Span<T> colors) public static IMinMax MinMax<T>(this Span<T> colors)
where T : struct, IColor where T : struct, IColor
=> T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors));
public static IMinMax MinMax<T>(this ReadOnlySpan<T> colors)
where T : struct, IColor
=> T.ColorFormat.MinMax(MemoryMarshal.AsBytes(colors));
internal static IMinMax MinMax<T, TMinMax>(ReadOnlySpan<T> colors)
where T : struct, IColor
where TMinMax : struct, IMinMax
{ {
if (colors == null) throw new ArgumentNullException(nameof(colors)); if (colors == null) throw new ArgumentNullException(nameof(colors));
return T.ColorFormat.BytesPerPixel switch IColorFormat colorFormat = T.ColorFormat;
return colorFormat.BytesPerPixel switch
{ {
3 => Unsafe.BitCast<Generic3ByteMinMax, TMinMax>(MinMax(MemoryMarshal.Cast<T, Generic3ByteData>(colors))), 3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
4 => Unsafe.BitCast<Generic4ByteMinMax, TMinMax>(MinMax(MemoryMarshal.Cast<T, Generic4ByteData>(colors))), 4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
_ => throw new NotSupportedException("Data is not of a supported valid color-type.") _ => throw new NotSupportedException("Data is not of a supported valid color-type.")
}; };
} }
private static Generic3ByteMinMax MinMax(ReadOnlySpan<Generic3ByteData> data) public static IMinMax MinMax<T>(this ReadOnlySpan<T> colors)
where T : struct, IColor
{ {
byte minB1 = byte.MaxValue, minB2 = byte.MaxValue, minB3 = byte.MaxValue; if (colors == null) throw new ArgumentNullException(nameof(colors));
byte maxB1 = byte.MinValue, maxB2 = byte.MinValue, maxB3 = byte.MinValue;
IColorFormat colorFormat = T.ColorFormat;
return colorFormat.BytesPerPixel switch
{
3 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic3ByteData>(colors))),
4 => colorFormat.ToMinMax(MinMax(MemoryMarshal.Cast<T, Generic4ByteData>(colors))),
_ => throw new NotSupportedException("Data is not of a supported valid color-type.")
};
}
private static Generic3ByteMinMax MinMax(ReadOnlySpan<Generic3ByteData> data) => MinMax(data, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue, byte.MaxValue, byte.MinValue);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Generic3ByteMinMax MinMax(ReadOnlySpan<Generic3ByteData> data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3)
{
const int BYTES_PER_COLOR = 3; const int BYTES_PER_COLOR = 3;
int elementsPerVector = Vector<byte>.Count / BYTES_PER_COLOR; int elementsPerVector = Vector<byte>.Count / BYTES_PER_COLOR;
@ -166,11 +193,12 @@ public static unsafe partial class PixelHelper
return new Generic3ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3); return new Generic3ByteMinMax(minB1, maxB1, minB2, maxB2, minB3, maxB3);
} }
private static Generic4ByteMinMax MinMax(ReadOnlySpan<Generic4ByteData> 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<Generic4ByteData> 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<Generic4ByteData> data, byte minB1, byte maxB1, byte minB2, byte maxB2, byte minB3, byte maxB3, byte minB4, byte maxB4)
{
const int BYTES_PER_COLOR = 4; const int BYTES_PER_COLOR = 4;
int elementsPerVector = Vector<byte>.Count / BYTES_PER_COLOR; int elementsPerVector = Vector<byte>.Count / BYTES_PER_COLOR;