// 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;
}
}
}