How to connect a common instance of Span <T> to work with it using Parallel.For?
I am rewriting some of my extension methods using the new Span<T> , and I am having trouble finding a way to correctly associate a shared instance so that I can 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 in Span<T> input using some value provider, and since these vectors can be quite large, I would like to fill them in parallel.
This is just an example, so even if using parallel code here is not 100% necessary, the question is still there, since I will need to use parallel code again or later, anyway.
Now this code really works, but since I never fix the input range and given the fact that it may well point to some controllable vector T[] , which can be moved all the time using GC, I think Iām just lucky that it works great in my tests.
So my question is:
Is it possible to bind a common
Span<T>instance and get a simplevoid*pointer to it so that I can pass it in closure to work with theSpan<T>instance in parallel code?
Thanks!
I think I might have found a workaround using one of the new methods in the Unsafe class, I tested it and so far seems to work. Here he 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 cannot bind the value of ref T , I tried to get the variable ref byte using Unsafe.As<T, byte>(ref T value) and bind it. Since it points to the same address, I think (hopefully) that it is anchored just fine, it should do the same in IL.