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 simplevoid*
pointer to it, so that I can pass it around in closures to work on 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'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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With