6
\$\begingroup\$

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;
        }
    }         
    
    
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Not worth an actual answer... you should make Pred and VPred readonly structs. It opens up some additional optimisation opportunities for the compiler (e.g. preventing defensive copies) which may be beneficial. \$\endgroup\$ – RobH Oct 7 '20 at 18:29
  • 1
    \$\begingroup\$ This isn't enough for an answer, so I'll post a comment instead: Naming guidelines. .NET favours readability over brevity. Instead of struct VPred it should (probably) be struct VectorPredicate, instead of SimdOps it should be SimdOperations. You should also not use abbreviations, so instead of GetRef it should be GetReference. .NET itself breaks some of these guidelines at some points (e.g IPAddress), but it's good to stick to them yourself. source \$\endgroup\$ – MindSwipe Oct 8 '20 at 7:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.