Pages

Monday, July 7, 2014

Vote for "Unmanaged generic type constraint + generic pointers" uservoice idea for .NET

If you care about performance, maybe because you are working with big data, image analysis, computer vision, machine learning etc., please go to Visual Studio user voice and vote for:


Microsoft are taking these user voice ideas serious (very good) and are in fact currently working on C# and SIMD in the form of, at least for now, an out-of-band library Microsoft.Bcl.Simd and the new Just-in-Time compiler RyuJIT.

Getting support for an unmanaged generic type constraint and generic pointers combined with SIMD operations based on unsafe pointers would be absolutely awesome. Although, the System.Numerics.Vector types and methods in the current version 1.0.2-beta of Microsoft.Bcl.Simd are very limited in type support and have no support for creating "vectors" over pointers.

Currently, the lack of unmanaged generic type constraint and generic pointers means that, if you are doing image processing on different primitive types byte, sbyte, short, ushort, int, uint, long, ulong, float, double etc., then you have to repeat code for each of these - if you care about type safety and performance - leading to massive code bloat and combinatorial explosions where I have seen methods with thousands of overloads (using T4 code generation), which Visual Studios intellisense simply can't handle. Getting the above feature would allow a lot of this code to be boiled down to a single generic method e.g. (a draft example using value type trick to get inlining inside execution loop):

    public interface IFunc<in T, out TResult>
    {
        TResult Invoke(T arg);
    }

    public struct Threshold : IFunc<int, byte>
    {
        readonly int m_threshold;

        public Threshold(int threshold)
        {
            m_threshold = threshold;
        }

        // Since this is a value type it will be inlined by JIT in release
        public byte Invoke(int value)
        {
            return value > m_threshold ? byte.MaxValue : byte.MinValue;
        }
    }

    public static class Transforms
    {
        public unsafe static TFunc Transform<T, TResult, TFunc>(
            ArrayPtr2D<T> src, TFunc func, ArrayPtr2D<TResult> dst)
            where T : unmanaged
            where TFunc : struct, IFunc<T, TResult>
        {
            if (src.Size != dst.Size)
            { throw new ArgumentException("Arrays must have same size"); }

            var width = src.Size.Width;
            var height = src.Size.Height;

            var srcStride = src.StrideInBytes;
            T* srcRowPtr = src.DataPtr;
            var srcRowPtrEnd = ((byte*)srcRowPtr) + srcStride * height;

            var dstStride = dst.StrideInBytes;
            TResult* dstRowPtr = dst.DataPtr;

            for (; srcRowPtr != srcRowPtrEnd;
                   srcRowPtr = (T*)(((byte*)srcRowPtr) + srcStride),
                   dstRowPtr = (TResult*)(((byte*)dstRowPtr) + dstStride))
            {
                var srcColPtr = srcRowPtr;
                var srcColPtrEnd = srcColPtr + width;
                var dstColPtr = dstRowPtr;
                for (; srcColPtr != srcColPtrEnd; ++srcColPtr, ++dstColPtr)
                {
                    *dstColPtr = func.Invoke(*srcColPtr);
                }
            }
            return func;
        }
    }

    public class Program
    {
        public static void Main()
        {
            // Initialize from some existing native data i.e. from a bitmap etc.
            ArrayPtr2D<int> src = ...;
            ArrayPtr2D<byte> dst = ...;
            // Unsafe, fast, loop with threshold invoke inlined
            Transforms.Transform(src, new Threshold(1275), dst); 
        }
    }
Note how due to the transform having type 'T' as input and type 'TResult' as output we can handle a lot of combinations with this, without repeating the code. The JIT will, of course, have to generate specific code for each actual type usage etc. However, this is exactly what we want. Optimized code for each specific type combination and value type func. Who wouldn't want that ;)

F# already has the unmanaged generic type constraint as can be seen in Constraints (F#), we just need this in C# and generic pointers to unmanaged types.

So perhaps instead of obsessing about Change All CAPS Menu in VS 2012 to VS Beta format File Edit Instead of FILE EDIT (although I agree ALL CAPS is a terrible design choice) vote for something that we could all enjoy, less code and higher performance ;)