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

a 夏天 提交于 2020-01-02 01:56:12

问题


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!


回答1:


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.



来源:https://stackoverflow.com/questions/48083037/how-to-pin-a-generic-spant-instance-to-work-on-it-with-parallel-for

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!