// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using BenchmarkDotNet.Attributes; using SixLabors.ImageSharp.PixelFormats.Utils; namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion; /// /// Compares operator-driven affine vector transforms with the duplicated traversals they replace. /// [Config(typeof(Config.Short))] public class Vector4AffineTransform { private static readonly Vector4 Multiplier = new(255F, 2F, 65535F, .5F); private static readonly Vector4 Offset = new(17F, -1F, 32768F, 3F); private static readonly Vector4 Divisor = new(255F, 2F, 65535F, .5F); private Vector4[] current; private Vector4[] baseline; /// /// Gets or sets the number of vectors transformed by each invocation. /// [Params(1, 3, 4, 17, 256, 4096)] public int Count { get; set; } /// /// Creates identical non-uniform buffers for the current and baseline traversals. /// [GlobalSetup] public void Setup() { this.current = new Vector4[this.Count]; for (int i = 0; i < this.current.Length; i++) { this.current[i] = new Vector4(i + .25F, i + .5F, i + .75F, i + 1F); } this.baseline = [.. this.current]; } /// /// Executes the operator-driven multiply-then-add traversal. /// [Benchmark] public void CurrentMultiplyThenAdd() => Vector4Converters.MultiplyThenAdd(this.current, Multiplier, Offset); /// /// Executes the duplicated multiply-then-add traversal. /// [Benchmark(Baseline = true)] public void BaselineMultiplyThenAdd() => BaselineMultiplyThenAdd(this.baseline, Multiplier, Offset); /// /// Executes the operator-driven add-then-divide traversal. /// [Benchmark] public void CurrentAddThenDivide() => Vector4Converters.AddThenDivide(this.current, Offset, Divisor); /// /// Executes the duplicated add-then-divide traversal. /// [Benchmark] public void BaselineAddThenDivide() => BaselineAddThenDivide(this.baseline, Offset, Divisor); /// /// Retains the multiply-then-add traversal being replaced for direct measurement. /// /// The vectors to transform. /// The component-wise multiplier. /// The component-wise offset. internal static void BaselineMultiplyThenAdd(Span vectors, Vector4 multiplier, Vector4 offset) { ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); int index = 0; if (Vector512.IsHardwareAccelerated) { int vectorsPerVector = Vector512.Count / Vector128.Count; Vector256 multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128()); Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); Vector512 multiplier512 = Vector512.Create(multiplier256, multiplier256); Vector512 offset512 = Vector512.Create(offset256, offset256); for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) { ref Vector512 vector = ref Unsafe.As>( ref Unsafe.Add(ref vectorBase, (uint)index)); vector = (vector * multiplier512) + offset512; } } if (Vector256.IsHardwareAccelerated) { int vectorsPerVector = Vector256.Count / Vector128.Count; Vector256 multiplier256 = Vector256.Create(multiplier.AsVector128(), multiplier.AsVector128()); Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) { ref Vector256 vector = ref Unsafe.As>( ref Unsafe.Add(ref vectorBase, (uint)index)); vector = (vector * multiplier256) + offset256; } } if (Vector128.IsHardwareAccelerated) { Vector128 multiplier128 = multiplier.AsVector128(); Vector128 offset128 = offset.AsVector128(); for (; index < vectors.Length; index++) { ref Vector128 vector = ref Unsafe.As>( ref Unsafe.Add(ref vectorBase, (uint)index)); vector = (vector * multiplier128) + offset128; } return; } for (; index < vectors.Length; index++) { ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); vector = (vector * multiplier) + offset; } } /// /// Retains the add-then-divide traversal being replaced for direct measurement. /// /// The vectors to transform. /// The component-wise offset. /// The component-wise divisor. internal static void BaselineAddThenDivide(Span vectors, Vector4 offset, Vector4 divisor) { ref Vector4 vectorBase = ref MemoryMarshal.GetReference(vectors); int index = 0; if (Vector512.IsHardwareAccelerated) { int vectorsPerVector = Vector512.Count / Vector128.Count; Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); Vector256 divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128()); Vector512 offset512 = Vector512.Create(offset256, offset256); Vector512 divisor512 = Vector512.Create(divisor256, divisor256); for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) { ref Vector512 vector = ref Unsafe.As>( ref Unsafe.Add(ref vectorBase, (uint)index)); vector = (vector + offset512) / divisor512; } } if (Vector256.IsHardwareAccelerated) { int vectorsPerVector = Vector256.Count / Vector128.Count; Vector256 offset256 = Vector256.Create(offset.AsVector128(), offset.AsVector128()); Vector256 divisor256 = Vector256.Create(divisor.AsVector128(), divisor.AsVector128()); for (; index <= vectors.Length - vectorsPerVector; index += vectorsPerVector) { ref Vector256 vector = ref Unsafe.As>( ref Unsafe.Add(ref vectorBase, (uint)index)); vector = (vector + offset256) / divisor256; } } if (Vector128.IsHardwareAccelerated) { Vector128 offset128 = offset.AsVector128(); Vector128 divisor128 = divisor.AsVector128(); for (; index < vectors.Length; index++) { ref Vector128 vector = ref Unsafe.As>( ref Unsafe.Add(ref vectorBase, (uint)index)); vector = (vector + offset128) / divisor128; } return; } for (; index < vectors.Length; index++) { ref Vector4 vector = ref Unsafe.Add(ref vectorBase, (uint)index); vector = (vector + offset) / divisor; } } }