From f4c1eaddd6fe7a3ecfe62d4c9e9ff8ab7a5e170d Mon Sep 17 00:00:00 2001 From: Darth Affe Date: Mon, 19 Aug 2024 22:19:25 +0200 Subject: [PATCH] Improved performance of Sum on Images --- HPPH.Benchmark/BenchmarkHelper.cs | 14 +++ HPPH.Benchmark/SumBenchmarks.cs | 25 ++++++ HPPH.Generators/Features/Sum.cs | 7 +- .../ColorFormatABGR.Sum.g.cs | 4 +- .../ColorFormatARGB.Sum.g.cs | 4 +- .../ColorFormatBGR.Sum.g.cs | 4 +- .../ColorFormatBGRA.Sum.g.cs | 4 +- .../ColorFormatRGB.Sum.g.cs | 4 +- .../ColorFormatRGBA.Sum.g.cs | 4 +- .../IColorFormat.Sum.g.cs | 3 +- HPPH/Images/ImageRow.cs | 3 + HPPH/Images/Interfaces/IImageRow.cs | 2 + HPPH/PixelHelper.Quantize.cs | 4 +- HPPH/PixelHelper.Sum.cs | 87 +++++++------------ HPPH/PixelHelper.cs | 5 +- 15 files changed, 103 insertions(+), 71 deletions(-) diff --git a/HPPH.Benchmark/BenchmarkHelper.cs b/HPPH.Benchmark/BenchmarkHelper.cs index 4763793..085f558 100644 --- a/HPPH.Benchmark/BenchmarkHelper.cs +++ b/HPPH.Benchmark/BenchmarkHelper.cs @@ -21,4 +21,18 @@ internal static class BenchmarkHelper return colors; } + + public static List> GetSampleDataImages() + where T : struct, IColor + { + if (!Directory.Exists(SAMPLE_DATA_DIR)) throw new Exception("sample data not found!"); + + List> colors = []; + + IEnumerable files = Directory.EnumerateFiles(SAMPLE_DATA_DIR, "*.png", SearchOption.AllDirectories); + foreach (string file in files) + colors.Add(ImageHelper.LoadImage(file).ConvertTo()); + + return colors; + } } \ No newline at end of file diff --git a/HPPH.Benchmark/SumBenchmarks.cs b/HPPH.Benchmark/SumBenchmarks.cs index bc7d105..6b4d495 100644 --- a/HPPH.Benchmark/SumBenchmarks.cs +++ b/HPPH.Benchmark/SumBenchmarks.cs @@ -13,6 +13,8 @@ public class SumBenchmarks private readonly List _colors3bpp; private readonly List _colors4bpp; + private readonly List> _images3bpp; + private readonly List> _images4bpp; #endregion @@ -22,6 +24,9 @@ public class SumBenchmarks { _colors3bpp = BenchmarkHelper.GetSampleData(); _colors4bpp = BenchmarkHelper.GetSampleData(); + + _images3bpp = BenchmarkHelper.GetSampleDataImages(); + _images4bpp = BenchmarkHelper.GetSampleDataImages(); } #endregion @@ -48,6 +53,26 @@ public class SumBenchmarks return sums; } + [Benchmark] + public ISum[] PixelHelper_3BPP_Image() + { + ISum[] sums = new ISum[_colors3bpp.Count]; + for (int i = 0; i < _images3bpp.Count; i++) + sums[i] = _images3bpp[i].Sum(); + + return sums; + } + + [Benchmark] + public ISum[] PixelHelper_4BPP_Image() + { + ISum[] sums = new ISum[_images4bpp.Count]; + for (int i = 0; i < _images4bpp.Count; i++) + sums[i] = _images4bpp[i].Sum(); + + return sums; + } + [Benchmark] public ISum[] Reference_3BPP() { diff --git a/HPPH.Generators/Features/Sum.cs b/HPPH.Generators/Features/Sum.cs index cd12f2d..f733286 100644 --- a/HPPH.Generators/Features/Sum.cs +++ b/HPPH.Generators/Features/Sum.cs @@ -106,6 +106,7 @@ internal class Sum : IGeneratorFeature return $$""" #nullable enable + using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace HPPH; @@ -114,7 +115,8 @@ internal class Sum : IGeneratorFeature { #region Methods - unsafe ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } @@ -130,7 +132,8 @@ internal class Sum : IGeneratorFeature public partial interface IColorFormat { - internal ISum Sum(ReadOnlySpan data); + internal Generic4LongData Sum(ReadOnlySpan data); + internal ISum ToSum(Generic4LongData data); } """; } diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs index 6f302dc..c4f92e2 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatABGR.Sum.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 ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs index f9dd682..c527ebe 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatARGB.Sum.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 ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs index 0655069..54fd3c8 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGR.Sum.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 ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs index 61bcff4..67493c5 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatBGRA.Sum.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 ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs index 960115d..20e205a 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGB.Sum.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 ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs index b9ea3ee..e5696b9 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/ColorFormatRGBA.Sum.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 ISum IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe Generic4LongData IColorFormat.Sum(ReadOnlySpan data) => PixelHelper.Sum(MemoryMarshal.Cast(data)); + unsafe ISum IColorFormat.ToSum(Generic4LongData data) => Unsafe.BitCast(data); #endregion } \ No newline at end of file diff --git a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs index d924d82..cdfe18e 100644 --- a/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs +++ b/HPPH/Generated/HPPH.Generators/HPPH.Generators.ColorSourceGenerator/IColorFormat.Sum.g.cs @@ -4,5 +4,6 @@ namespace HPPH; public partial interface IColorFormat { - internal ISum Sum(ReadOnlySpan data); + internal Generic4LongData Sum(ReadOnlySpan data); + internal ISum ToSum(Generic4LongData data); } \ No newline at end of file diff --git a/HPPH/Images/ImageRow.cs b/HPPH/Images/ImageRow.cs index 2bc42da..d48294f 100644 --- a/HPPH/Images/ImageRow.cs +++ b/HPPH/Images/ImageRow.cs @@ -156,6 +156,9 @@ internal sealed class IColorImageRow : IImageRow #region Methods + /// + public ReadOnlySpan AsByteSpan() => _buffer.AsSpan().Slice(_start, SizeInBytes); + /// public void CopyTo(Span destination) { diff --git a/HPPH/Images/Interfaces/IImageRow.cs b/HPPH/Images/Interfaces/IImageRow.cs index ae15708..1f1cb82 100644 --- a/HPPH/Images/Interfaces/IImageRow.cs +++ b/HPPH/Images/Interfaces/IImageRow.cs @@ -22,6 +22,8 @@ public interface IImageRow : IEnumerable /// The at the specified location. IColor this[int x] { get; } + ReadOnlySpan AsByteSpan(); + void CopyTo(Span destination); /// diff --git a/HPPH/PixelHelper.Quantize.cs b/HPPH/PixelHelper.Quantize.cs index 2908de5..5a23eca 100644 --- a/HPPH/PixelHelper.Quantize.cs +++ b/HPPH/PixelHelper.Quantize.cs @@ -245,8 +245,6 @@ public static partial class PixelHelper ColorCube[] cubes = new ColorCube[1 << splits]; cubes[0] = new ColorCube(0, colors.Length, SortTarget.None); - ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount }; - int colorsLength = colors.Length; fixed (T* colorsPtr = colors) { @@ -257,7 +255,7 @@ public static partial class PixelHelper { int currentCubeCount = 1 << i; - Parallel.For(0, currentCubeCount, parallelOptions, CreateCubes); + Parallel.For(0, currentCubeCount, PARALLEL_OPTIONS, CreateCubes); void CreateCubes(int index) { diff --git a/HPPH/PixelHelper.Sum.cs b/HPPH/PixelHelper.Sum.cs index ead249b..2f41d8c 100644 --- a/HPPH/PixelHelper.Sum.cs +++ b/HPPH/PixelHelper.Sum.cs @@ -1,7 +1,6 @@ using System.Runtime.InteropServices; using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics; -using System.Buffers; using System.Runtime.CompilerServices; namespace HPPH; @@ -14,29 +13,20 @@ public static unsafe partial class PixelHelper { ArgumentNullException.ThrowIfNull(image); - int dataLength = image.SizeInBytes; + IColorFormat colorFormat = image.ColorFormat; - if (dataLength <= 1024) - { - Span buffer = stackalloc byte[dataLength]; + if (image.Height == 0) return colorFormat.ToSum(new Generic4LongData(0, 0, 0, 0)); + if (image.Height == 1) return colorFormat.ToSum(colorFormat.Sum(image.Rows[0].AsByteSpan())); - image.CopyTo(buffer); - return image.ColorFormat.Sum(buffer); - } - else + Vector256 result = Vector256.Zero; + for (int y = 0; y < image.Height; y++) { - byte[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..dataLength]; - try - { - image.CopyTo(buffer); - return image.ColorFormat.Sum(buffer); - } - finally - { - ArrayPool.Shared.Return(array); - } + Generic4LongData rowSum = colorFormat.Sum(image.Rows[y].AsByteSpan()); + Vector256 rowSumVector = Vector256.LoadUnsafe(ref Unsafe.As(ref rowSum)); + result = Vector256.Add(result, rowSumVector); } + + return colorFormat.ToSum(Unsafe.BitCast, Generic4LongData>(result)); } public static ISum Sum(this IImage image) @@ -50,56 +40,37 @@ public static unsafe partial class PixelHelper public static ISum Sum(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) - { - Span buffer = MemoryMarshal.Cast(stackalloc byte[sizeInBytes]); + if (image.Height == 0) return colorFormat.ToSum(new Generic4LongData(0, 0, 0, 0)); + if (image.Height == 1) return colorFormat.ToSum(colorFormat.Sum(image.Rows[0].AsByteSpan())); - image.CopyTo(buffer); - return Sum(buffer); - } - else + Vector256 result = Vector256.Zero; + for (int y = 0; y < image.Height; y++) { - T[] array = ArrayPool.Shared.Rent(dataLength); - Span buffer = array.AsSpan()[..(dataLength)]; - try - { - image.CopyTo(buffer); - return Sum(buffer); - } - finally - { - ArrayPool.Shared.Return(array); - } + Generic4LongData rowSum = colorFormat.Sum(image.Rows[y].AsByteSpan()); + Vector256 rowSumVector = Vector256.LoadUnsafe(ref Unsafe.As(ref rowSum)); + result = Vector256.Add(result, rowSumVector); } + + return colorFormat.ToSum(Unsafe.BitCast, Generic4LongData>(result)); } public static ISum Sum(this ReadOnlySpan colors) where T : struct, IColor - => T.ColorFormat.Sum(MemoryMarshal.AsBytes(colors)); + { + IColorFormat colorFormat = T.ColorFormat; + return colorFormat.ToSum(colorFormat.Sum(MemoryMarshal.AsBytes(colors))); + } public static ISum Sum(this Span colors) where T : struct, IColor - => T.ColorFormat.Sum(MemoryMarshal.AsBytes(colors)); - - internal static ISum Sum(ReadOnlySpan colors) - where T : struct, IColor - where TSum : struct, ISum { - if (colors == null) throw new ArgumentNullException(nameof(colors)); - - return T.ColorFormat.BytesPerPixel switch - { - // DarthAffe 05.07.2024: Important: The sum of 3-byte colors result in 4 byte data! - 3 => Unsafe.BitCast(Sum(MemoryMarshal.Cast(colors))), - 4 => Unsafe.BitCast(Sum(MemoryMarshal.Cast(colors))), - _ => throw new NotSupportedException("Data is not of a supported valid color-type.") - }; + IColorFormat colorFormat = T.ColorFormat; + return colorFormat.ToSum(colorFormat.Sum(MemoryMarshal.AsBytes(colors))); } - - private static Generic4LongData Sum(ReadOnlySpan data) + + internal static Generic4LongData Sum(ReadOnlySpan data) { long b1Sum = 0, b2Sum = 0, b3Sum = 0; @@ -215,7 +186,7 @@ public static unsafe partial class PixelHelper return new Generic4LongData(b1Sum, b2Sum, b3Sum, data.Length * 255); } - private static Generic4LongData Sum(ReadOnlySpan data) + internal static Generic4LongData Sum(ReadOnlySpan data) { long b1Sum, b2Sum, b3Sum, b4Sum; int i = 0; diff --git a/HPPH/PixelHelper.cs b/HPPH/PixelHelper.cs index a15aaca..2e13ae4 100644 --- a/HPPH/PixelHelper.cs +++ b/HPPH/PixelHelper.cs @@ -3,4 +3,7 @@ namespace HPPH; [SkipLocalsInit] -public static partial class PixelHelper; +public static partial class PixelHelper +{ + private static readonly ParallelOptions PARALLEL_OPTIONS = new() { MaxDegreeOfParallelism = Environment.ProcessorCount }; +}