The problem here is that a Span<T>
cannot be allowed onto the heap - it is only valid on the stack - which means that it can't be either boxed, or used as a field on a class
(or a struct
except a ref struct
). This rules out both captured variables and most common forms of state
parameters.
If you have the luxury of changing the input to be a memory, you can capture the memory, and get the span inside the lambda body:
void ParallelDoSomething(Memory<int> memory)
{
var size = memory.Length;
Parallel.Invoke(
() => DoSomething(memory.Span, 0, size / 2),
() => DoSomething(memory.Span, size/2, size)
);
}
If you can't change the input, you can still do it... by cheating. You can pin the existing span, create a memory that covers that pinned data, and use that memory just like you would if it had been passed in as a memory. This isn't entirely trivial, since you need to write your own pointer-based memory manager implementation, but: it works. Here's an example from protobuf-net: https://github.com/protobuf-net/protobuf-net/blob/main/src/protobuf-net.Core/Meta/TypeModel.cs#L767-L789
Or perhaps more conveniently, pin the span and capture the pointer directly, noting that the compiler doesn't normally allow this (to prevent the pointer being used by a delegate later), but since we know the timing semantics, we can make it happy by duplicating the pointer:
unsafe void ParallelDoSomething(Span<int> span)
{
var size = span.Length;
fixed (int* ptr = span)
{
int* evil = ptr; // make the compiler happy
Parallel.Invoke(
() => DoSomething(new Span<int>(evil, size), 0, size / 2),
() => DoSomething(new Span<int>(evil, size), size / 2, size)
);
}
}
or if we wanted to fix slice the span at the point of input:
unsafe void ParallelDoSomething(Span<int> span)
{
var size = span.Length;
fixed (int* ptr = span)
{
int* evil = ptr; // make the compiler happy
Parallel.Invoke(
() => DoSomething(new Span<int>(evil, size / 2));
() => DoSomething(new Span<int>(evil + (size/2), size - (size/2));
);
}
}
option1
/option2
should be removed fromDoSomething()
; that looks like something that should be applied via slice, instead; as a side note, doing so can improve performance:for(int i = 0 ; i < span.Length; i++)
andforeach(var val in span)
have JIT optimizations (bounds checks elision), butfor (int i = option1; i < option2; i++)
does not – HangnailParallelDoSomething
instead – Anthesisbuffer
created? – AnthesisSpan
inside each thread then merge at the end, maybe? – Anthesis