93

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?

Rand Random
  • 7,300
  • 10
  • 40
  • 88
Dan Sorensen
  • 11,403
  • 19
  • 67
  • 100
  • 4
    See also: [C# 7.2: Understanding Span](https://channel9.msdn.com/Events/Connect/2017/T125) – Jeff Mercado Nov 16 '17 at 04:58
  • 2
    @JeffMercado Is there a full list somewhere of what C# types they can replace besides string[]? Are they only for arrays or can they be used instead of types like List as well? – DarthVegan Nov 22 '17 at 15:40

4 Answers4

81

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 complementing Span<T>. As discussed in its design document, Span<T> is a stack-only type. The stack-only nature of Span<T> makes it unsuitable for many scenarios that require storing references to buffers (represented with Span<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 unlike Span <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 returns Span<byte>, but the returned value does not get stored on the heap during asynchronous calls, but rather new values are produced from the Memory<T> value. In a sense, Memory<T> is a factory of Span<T>.

Reference Document: here

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
Hussein Salman
  • 7,806
  • 15
  • 60
  • 98
  • 10
    What does "stack-only nature" mean? – Enigmativity Nov 16 '17 at 05:36
  • 5
    Noob here. Wouldn't the stack be rolled back to where it was before the await? I didn't get why the buffer can't survive the continuation in the `Span buffer` example. Why do we loose the address to the point in memory where buffer[0] points to? – Spectraljump Aug 02 '18 at 09:50
  • 3
    @Spectraljump If I'm not mistaken, any variable in an async method will, in reality, be a class field once compiled (so, on the heap), in order to be able to use those values after an await. If the Span can only be on the stack, it wouldn't be the same Span before and after the await. – krimog Sep 25 '18 at 14:55
  • 2
    @Spectraljump An Async method will be converted to a statemachine. So the first part of the statemachine (before the await) is executed, it will exit the method. When the awaited task is completed, the next state will be executed (separate method), that's why the stack of the first state unwinds. – Jeroen van Langen Feb 07 '21 at 20:25
  • Why does the document say "at the cost of **significant performance penalties** for reading and writing to the memory" if we're _meant_ to use `Memory` in many cases where we can't use `Span`/`ReadOnlySpan`? – Dai Jan 19 '22 at 12:30
  • span within async gives error – Saurabh Nov 25 '22 at 11:42
  • @Dai I was to ask the exact same thing, when the Memory can be as contigoeus in the heap/unmanaged region as the data of a Span is?? Where is the logic in that phrase.. Ofc I do know that Memory can point to ANY memory in the heap sector, so they do not have to be in next order, so when that is the case the phrase is correct, but if it is legit an managed array having its fields laid out next in memory, its as fast as span reading writing – Shpendicus Apr 17 '23 at 04:30
45

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.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
  • Ah, another one of those byref-like types. – IS4 Nov 29 '17 at 09:25
  • 1
    But why can it only reside on Stack? And why cannot you box them? or cannot put them into class? – ANewGuyInTown May 01 '18 at 23:48
  • 6
    If you could get a Span anywhere on the heap (via boxing, member of a class...), then you'd have an object on a heap that could point to memory on the stack, which would be freed when, say, a function has returned. Then you'd have a heap-object that points to freed memory. that could lead to a segfault. – Mor A. Jun 29 '18 at 11:31
  • 6
    @M.Aroosi - isn't that the same case for Memory? Memory can reside on the heap, and it can point to memory in the stack, no? New to these two and trying to understand it. – p e p Aug 03 '18 at 04:39
  • 5
    @pep You can't assign stack-allocated buffer to `Memory` (at least not *directly*) you can try this out `Memory mem = stackalloc byte[100];` and get compilation error – Shmil The Cat Oct 11 '19 at 21:39
5

General

Span defined as a reference struct and Memory is defined as a struct

What does it mean?

  • Reference structs can't be stored on a heap, compiler will prevent you from doing that, so following won't be allowed:

    • Using Span as a field in a class
    • Using Span in a async method, (async methods are being expanded in a full-blown state machine)
    • and many more, here is the complete list of things that can't be done with reference structs.
  • 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];
    

What does it imply?

It means that in certain scenarios various micro optimization are not possible with Span itself and therefore Memory should be used instead.

Example:

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 = 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.

Monsieur Merso
  • 1,459
  • 1
  • 15
  • 18
-10

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.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Olivier Duhart
  • 362
  • 3
  • 14