mirror of https://github.com/SixLabors/ImageSharp
5 changed files with 355 additions and 14 deletions
@ -0,0 +1,276 @@ |
|||
// 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; |
|||
|
|||
namespace SixLabors.ImageSharp.Common.Helpers; |
|||
|
|||
internal static partial class TensorPrimitives_ |
|||
{ |
|||
/// <summary>
|
|||
/// Defines an element-wise unary operation.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private interface IUnaryOperator<T> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets a value indicating whether the operation supports vector execution.
|
|||
/// </summary>
|
|||
public static abstract bool Vectorizable { get; } |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a scalar value.
|
|||
/// </summary>
|
|||
/// <param name="x">The input value.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract T Invoke(T x); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a 128-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The input vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector128<T> Invoke(Vector128<T> x); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a 256-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The input vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector256<T> Invoke(Vector256<T> x); |
|||
|
|||
/// <summary>
|
|||
/// Applies the operation to a 512-bit vector.
|
|||
/// </summary>
|
|||
/// <param name="x">The input vector.</param>
|
|||
/// <returns>The operation result.</returns>
|
|||
public static abstract Vector512<T> Invoke(Vector512<T> x); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Computes the element-wise negation of the values in <paramref name="x"/>.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <param name="x">The values to negate.</param>
|
|||
/// <param name="destination">The destination for the negated values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static void Negate<T>(ReadOnlySpan<T> x, Span<T> destination) |
|||
where T : IUnaryNegationOperators<T, T> |
|||
=> InvokeSpanIntoSpan<T, NegateOperator<T>>(x, destination); |
|||
|
|||
/// <summary>
|
|||
/// Performs an element-wise unary operation over a span.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="x">The input values.</param>
|
|||
/// <param name="destination">The destination values.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeSpanIntoSpan<T, TOperator>(ReadOnlySpan<T> x, Span<T> destination) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
ref T xRef = ref MemoryMarshal.GetReference(x); |
|||
ref T destinationRef = ref MemoryMarshal.GetReference(destination); |
|||
nuint length = (uint)x.Length; |
|||
|
|||
// The dispatch matches the other compatibility pipelines: AVX-512 is reserved for large spans because
|
|||
// its setup cost is not recovered by the short image-processing buffers that dominate ImageSharp.
|
|||
if (TOperator.Vectorizable |
|||
&& Vector512.IsHardwareAccelerated |
|||
&& Vector512<T>.IsSupported |
|||
&& length >= 512) |
|||
{ |
|||
InvokeUnaryVectorized512<T, TOperator>(ref xRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector256.IsHardwareAccelerated && Vector256<T>.IsSupported && length >= (uint)Vector256<T>.Count) |
|||
{ |
|||
InvokeUnaryVectorized256<T, TOperator>(ref xRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
if (TOperator.Vectorizable && Vector128.IsHardwareAccelerated && Vector128<T>.IsSupported && length >= (uint)Vector128<T>.Count) |
|||
{ |
|||
InvokeUnaryVectorized128<T, TOperator>(ref xRef, ref destinationRef, length); |
|||
return; |
|||
} |
|||
|
|||
for (nuint i = 0; i < length; i++) |
|||
{ |
|||
Unsafe.Add(ref destinationRef, i) = TOperator.Invoke(Unsafe.Add(ref xRef, i)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a unary operation with 128-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeUnaryVectorized128<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector128<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
|
|||
// The final vector overlaps the preceding store when the length is not a vector multiple. Loading it
|
|||
// before any stores preserves same-start in-place operation because it captures the original tail.
|
|||
Vector128<T> end = default; |
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector128.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a unary operation with 256-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeUnaryVectorized256<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector256<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector256<T> end = default; |
|||
|
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector256.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Applies a unary operation with 512-bit vectors.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
/// <typeparam name="TOperator">The operation to apply.</typeparam>
|
|||
/// <param name="xRef">The first input element.</param>
|
|||
/// <param name="destinationRef">The first destination element.</param>
|
|||
/// <param name="length">The number of elements to process.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static void InvokeUnaryVectorized512<T, TOperator>(ref T xRef, ref T destinationRef, nuint length) |
|||
where TOperator : struct, IUnaryOperator<T> |
|||
{ |
|||
nuint vectorCount = (uint)Vector512<T>.Count; |
|||
nuint vectorsPerLoop = vectorCount * 8; |
|||
nuint index = 0; |
|||
Vector512<T> end = default; |
|||
|
|||
if ((length % vectorCount) != 0) |
|||
{ |
|||
end = TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, length - vectorCount)); |
|||
} |
|||
|
|||
while ((length - index) >= vectorsPerLoop) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 0))).StoreUnsafe(ref destinationRef, index + (vectorCount * 0)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 1))).StoreUnsafe(ref destinationRef, index + (vectorCount * 1)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 2))).StoreUnsafe(ref destinationRef, index + (vectorCount * 2)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 3))).StoreUnsafe(ref destinationRef, index + (vectorCount * 3)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 4))).StoreUnsafe(ref destinationRef, index + (vectorCount * 4)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 5))).StoreUnsafe(ref destinationRef, index + (vectorCount * 5)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 6))).StoreUnsafe(ref destinationRef, index + (vectorCount * 6)); |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index + (vectorCount * 7))).StoreUnsafe(ref destinationRef, index + (vectorCount * 7)); |
|||
|
|||
index += vectorsPerLoop; |
|||
} |
|||
|
|||
while ((length - index) >= vectorCount) |
|||
{ |
|||
TOperator.Invoke(Vector512.LoadUnsafe(ref xRef, index)).StoreUnsafe(ref destinationRef, index); |
|||
index += vectorCount; |
|||
} |
|||
|
|||
if (index != length) |
|||
{ |
|||
end.StoreUnsafe(ref destinationRef, length - vectorCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Implements element-wise negation for scalar and SIMD inputs.
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The element type.</typeparam>
|
|||
private readonly struct NegateOperator<T> : IUnaryOperator<T> |
|||
where T : IUnaryNegationOperators<T, T> |
|||
{ |
|||
/// <inheritdoc />
|
|||
public static bool Vectorizable => true; |
|||
|
|||
/// <inheritdoc />
|
|||
public static T Invoke(T x) => -x; |
|||
|
|||
/// <inheritdoc />
|
|||
public static Vector128<T> Invoke(Vector128<T> x) => -x; |
|||
|
|||
/// <inheritdoc />
|
|||
public static Vector256<T> Invoke(Vector256<T> x) => -x; |
|||
|
|||
/// <inheritdoc />
|
|||
public static Vector512<T> Invoke(Vector512<T> x) => -x; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue