Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pin a generic Span<T> instance to work on it with Parallel.For?

Tags:

c#

.net

cil

c#-7.2

I'm rewriting some of my extension methods using the new Span<T> type and I'm having trouble finding a way to properly pin a generic instance to be able to use parallel code to work on it.

As an example, consider this extension method:

public static unsafe void Fill<T>(this Span<T> span, [NotNull] Func<T> provider) where T : struct
{
    int
        cores = Environment.ProcessorCount,
        batch = span.Length / cores,
        mod = span.Length % cores,
        sizeT = Unsafe.SizeOf<T>();
    //fixed (void* p0 = &span.DangerousGetPinnableReference()) // This doesn't work, can't pin a T object
    void* p0 = Unsafe.AsPointer(ref span.DangerousGetPinnableReference());
    {
        byte* p = (byte*)p0; // Local copy for the closure
        Parallel.For(0, cores, i =>
        {
            byte* start = p + i * batch * sizeT;
            for (int j = 0; j < batch; j++)
                Unsafe.Write(start + sizeT * j, provider());
        });

        // Remaining values
        if (mod == 0) return;
        for (int i = span.Length - mod; i < span.Length; i++)
            span[i] = provider();
    }
}

Here I just want to fill an input Span<T> using some values provider, and since these vectors could be quite large I'd like to populate them in parallel.

This is just an example, so even if using parallel code here isn't 100% necessary, the question still stands, as I'd need to use parallel code again sooner or later anyways.

Now, this code does work, but since I'm never actually pinning the input span and given the fact that it could very well be pointing to some managed T[] vector, which can be moved around all the time by the GC, I think I might have just been lucky to see it working fine in my tests.

So, my question is:

Is there any way to pin a generic Span<T> instance and get a simple void* pointer to it, so that I can pass it around in closures to work on the Span<T> instance in parallel code?

Thanks!

like image 208
Sergio0694 Avatar asked Jan 03 '18 18:01

Sergio0694


1 Answers

I think I might have found a workaround using one of the new methods in the Unsafe class, I've tested it and so far it seems to work. Here it is:

public static unsafe void Fill<T>(this Span<T> span, [NotNull] Func<T> provider) where T : struct
{
    int
        cores = Environment.ProcessorCount,
        batch = span.Length / cores,
        mod = span.Length % cores,
        size = Unsafe.SizeOf<T>();
    ref T r0 = ref span.DangerousGetPinnableReference();
    fixed (byte* p0 = &Unsafe.As<T, byte>(ref r0))
    {
        byte* p = p0;
        Parallel.For(0, cores, i =>
        {
            byte* pi = p + i * batch * size;
            for (int j = 0; j < batch; j++, pi += size)
                Unsafe.Write(pi, provider());
        }).AssertCompleted();

        // Remaining values
        if (mod < 1) return;
        for (int i = span.Length - mod; i < span.Length; i++)
            Unsafe.Write(p + i * size, provider());
    }
}

Basically, since I can't pin a ref T value, I tried to get a ref byte variable using Unsafe.As<T, byte>(ref T value) and to pin that one instead. Since it points to the same address, I think (hope) it's pinned just fine, it should be doing the same thing in IL.

like image 108
Sergio0694 Avatar answered Oct 13 '22 23:10

Sergio0694