I'm writing a library which simplifies the usage of intrinsics in C#.
It's a generic library which supports all numeric types.
The goal of this library is to perform these operations at the highest performance possible.
I'll gladly head any suggestions / tips which might improve the performance.
All feedback is welcome.
The whole library could be viewed at https://github.com/giladfrid009/SimpleSIMD
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SimpleSimd
{
// For value delegate pattern, which will allow to inline delegates.
// The performance benefits of this pattern is quite massive (up to 5x)
public interface IFunc<T, TResult>
{
TResult Invoke(T param);
}
// Example usage of value delegate pattern. These structs will be passed to the method
// instead of regular delegates.
// We use structs to prevent heap allocations
struct Pred : IFunc<int, bool>
{
int num;
public Pred(int num)
{
this.num = num;
}
public bool Invoke(int param)
{
return param == num;
}
}
struct VPred : IFunc<Vector<int>, bool>
{
public Vector<int> vec;
public VPred(Vector<int> vec)
{
this.vec = vec;
}
public bool Invoke(Vector<int> param)
{
return param == vec;
}
}
internal static class SimdOps
{
// Helper method to offset a ref, to eliminate bound checks when accessing an index
internal static ref U Offset<U>(this ref U source, int count) where U : struct
{
return ref Unsafe.Add(ref source, count);
}
}
public static partial class SimdOps<T> where T : unmanaged
{
// Helper method to get a ref to the first element of a span, without bound checks.
// Also, by getting a ref instead of a pointer, we no longer need to fix the object
private static ref U GetRef<U>(ReadOnlySpan<U> span) where U : unmanaged
{
return ref MemoryMarshal.GetReference(span);
}
// Helper method to convert ref type
private static ref Vector<U> AsVector<U>(in U value) where U : unmanaged
{
return ref Unsafe.As<U, Vector<U>>(ref Unsafe.AsRef(value));
}
// The actual method
public static int IndexOf<F1, F2>(in ReadOnlySpan<T> span, F1 vPredicate, F2 predicate)
where F1 : struct, IFunc<Vector<T>, bool>
where F2 : struct, IFunc<T, bool>
{
ref var rSpan = ref GetRef(span);
int i = 0;
if (Vector.IsHardwareAccelerated)
{
ref var vrSpan = ref AsVector(rSpan);
int length = span.Length / Vector<T>.Count;
for (; i < length; i++)
{
if (vPredicate.Invoke(vrSpan.Offset(i)))
{
int j = i * Vector<T>.Count;
int l = j + Vector<T>.Count;
for (; j < l; j++)
{
if (predicate.Invoke(rSpan.Offset(j)))
{
return j;
}
}
}
}
i *= Vector<T>.Count;
}
for (; i < span.Length; i++)
{
if (predicate.Invoke(rSpan.Offset(i)))
{
return i;
}
}
return -1;
}
}
}
Pred
andVPred
readonly structs. It opens up some additional optimisation opportunities for the compiler (e.g. preventing defensive copies) which may be beneficial. \$\endgroup\$struct VPred
it should (probably) bestruct VectorPredicate
, instead ofSimdOps
it should beSimdOperations
. You should also not use abbreviations, so instead ofGetRef
it should beGetReference
. .NET itself breaks some of these guidelines at some points (e.gIPAddress
), but it's good to stick to them yourself. source \$\endgroup\$