如何固定通用 Span<T> 实例以使用 Parallel.For 对其进行处理?

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

我正在使用新的 Span<T> 类型重写我的一些扩展方法,但我无法找到一种方法来正确固定通用实例,以便能够使用并行代码对其进行处理。

例如,考虑这种扩展方法:

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();
    }
}

这里我只想使用一些值提供程序填充输入 Span<T>,并且由于这些向量可能非常大,我想并行填充它们。

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.

现在,这段代码 可以工作 ,但是因为我从来没有真正固定输入跨度并且考虑到它很可能指向某个托管的事实 T[] vector,它可以一直被 GC 移动,我想我可能只是幸运地看到它在我的测试中运行良好。

那么,我的问题是:

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?

谢谢!

我想我可能已经找到了使用 Unsafe class 中的一种新方法的解决方法,我已经对其进行了测试,到目前为止它似乎有效。这是:

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());
    }
}

基本上,因为我无法固定 ref T 值,所以我尝试使用 Unsafe.As<T, byte>(ref T value) 获取 ref byte 变量并改为固定那个。由于它指向相同的地址,我认为(希望)它固定得很好,它应该在 IL 中做同样的事情。