Reputation: 11753
C# 7.2 introduces two new types: Span<T>
and Memory<T>
that have better performance over earlier C# types like string[]
.
Question: What is the difference between Span<T>
and Memory<T>
? Why would I use one over the other?
Upvotes: 103
Views: 34953
Reputation: 2176
Span defined as a reference struct and Memory is defined as a struct
Reference structs can't be stored on a heap, compiler will prevent you from doing that, so following won't be allowed:
stackalloc won't work with Memory (because there is no guarantee that it won't be stored on the heap) but will work with Span
// this is legit
Span<byte> data = stackalloc byte[256]; // legit
// compile time error: Conversion of a stackalloc expression of type 'byte' to type 'Memory<byte>' is not possible.
Memory<byte> data = stackalloc byte[256];
It means that in certain scenarios various micro optimization are not possible with Span itself and therefore Memory should be used instead.
Here is an example of an string allocation free Split method, which works on ReadOnlyMemory struct, implementing this on a Span would be really difficult, due to the fact that Span is a reference struct and can't be put into array or IEnumerable:
(Implementation is taken from C# in a nutshell book)
IEnumerable<ReadOnlyMemory<char>> Split(ReadOnlyMemory<char> input)
{
int wordStart = 0;
for (int i = 0; i <= input.Length; i++)
{
if (i == input.Length || char.IsWhiteSpace(input.Span[i]))
{
yield return input.Slice(wordStart, i - wordStart);
wordStart = i + 1;
}
}
}
And here are results of a very simple benchmark via dotnet benchmark library against regular Split method, on a .NET SDK=6.0.403.
| Method | StringUnderTest | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated |
|---------------------- |-------------------- |------------------:|------------------:|------------------:|----------:|----------:|---------:|-----------:|
| RegularSplit | meow | 13.194 ns | 0.2891 ns | 0.3656 ns | 0.0051 | - | - | 32 B |
| SplitOnReadOnlyMemory | meow | 8.991 ns | 0.1981 ns | 0.2433 ns | 0.0127 | - | - | 80 B |
| RegularSplit | meow(...)meow [499] | 1,077.807 ns | 21.2291 ns | 34.8801 ns | 0.6409 | 0.0095 | - | 4024 B |
| SplitOnReadOnlyMemory | meow(...)meow [499] | 9.036 ns | 0.2055 ns | 0.2366 ns | 0.0127 | - | - | 80 B |
| RegularSplit | meo(...)eow [49999] | 121,740.719 ns | 2,221.3079 ns | 2,077.8128 ns | 63.4766 | 18.5547 | - | 400024 B |
| SplitOnReadOnlyMemory | meo(...)eow [49999] | 9.048 ns | 0.2033 ns | 0.2782 ns | 0.0127 | - | - | 80 B |
| RegularSplit | me(...)ow [4999999] | 67,502,918.403 ns | 1,252,689.2949 ns | 2,092,962.4006 ns | 5625.0000 | 2375.0000 | 750.0000 | 40000642 B |
| SplitOnReadOnlyMemory | me(...)ow [4999999] | 9.160 ns | 0.2057 ns | 0.2286 ns | 0.0127 | - | - | 80 B |
Inputs for those methods were, strings of "meow " repeated 1, 100 , 10_000 and 1_000_000 times, my benchmark setup wasn't ideal but it shows the difference.
Upvotes: 10
Reputation: 447
Memory<T>
can be seen as an unsafe but more versatile version of Span<T>
. Access to a Memory<T>
object will fail if it points to a freed array.
Upvotes: -11
Reputation: 489
re: this means it can only point to memory allocated on the stack.
Span<T>
can point to any memory: allocated either on the stack or the heap. Stack-only nature of Span<T>
means that the Span<T>
itself (not the memory it points to) must reside only on the stack. This is in contrast to "normal" C# structs, that can reside on the stack or on the heap (either via value-type boxing, or when they are embedded in classes/reference-types). Some of the more obvious practical implications are that you cannot have a Span<T>
field in a class, you cannot box Span<T>
, and you cannot make an array of them.
Upvotes: 48
Reputation: 8246
Span<T>
is stack-only in nature while Memory<T>
can exist on the heap.
Span<T>
is a new type we are adding to the platform to represent contiguous regions of arbitrary memory, with performance characteristics on par with T[]. Its APIs are similar to the array, but unlike arrays, it can point to either managed or native memory, or to memory allocated on the stack.
Memory <T>
is a type complementingSpan<T>
. As discussed in its design document,Span<T>
is a stack-only type. The stack-only nature ofSpan<T>
makes it unsuitable for many scenarios that require storing references to buffers (represented withSpan<T>
) on the heap, e.g. for routines doing asynchronous calls.
async Task DoSomethingAsync(Span<byte> buffer) {
buffer[0] = 0;
await Something(); // Oops! The stack unwinds here, but the buffer below
// cannot survive the continuation.
buffer[0] = 1;
}
To address this problem, we will provide a set of complementary types, intended to be used as general purpose exchange types representing, just like
Span <T>
, a range of arbitrary memory, but unlikeSpan <T>
these types will not be stack-only, at the cost of significant performance penalties for reading and writing to the memory.
async Task DoSomethingAsync(Memory<byte> buffer) {
buffer.Span[0] = 0;
await Something(); // The stack unwinds here, but it's OK as Memory<T> is
// just like any other type.
buffer.Span[0] = 1;
}
In the sample above, the
Memory <byte>
is used to represent the buffer. It is a regular type and can be used in methods doing asynchronous calls. Its Span property returnsSpan<byte>
, but the returned value does not get stored on the heap during asynchronous calls, but rather new values are produced from theMemory<T>
value. In a sense,Memory<T>
is a factory ofSpan<T>
.
Reference Document: here
Upvotes: 92