Loading...

Performance Optimization with Span<T> and Memory<T> in C#

Learn performance optimization in C# using Span<T> and Memory<T> to manage memory efficiently and process data faster.

With C# 7.2 and later, the Span<T> and Memory<T> types optimize memory access in high-performance data processing scenarios by preventing unnecessary copies. Thanks to these types, you can work over large arrays, strings, or byte buffers without extra memory allocations.


What Is Span<T>?

Span<T> is a stack-based type that operates over memory using the concept of slicing. It holds a reference to a portion of an array, string, or unmanaged memory without copying. However, it lives on the stack; therefore it cannot be used in async methods or in constructs that escape to the heap.


using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 10, 20, 30, 40, 50 };

        Span<int> slice = numbers.AsSpan(1, 3); // 20,30,40
        slice[0] = 99;

        Console.WriteLine(string.Join(", ", numbers)); 
        // Output: 10, 99, 30, 40, 50 — because it's pointing to the same memory
    }
}

Access Without Copies via Slice

The key benefit of Span<T> is accessing subranges without copying data. For example, when you want to work on a specific part of a large byte array, you don’t need to create a new array.


byte[] buffer = new byte[1000];
Span<byte> header = buffer.AsSpan(0, 128); // header section
Span<byte> data = buffer.AsSpan(128);      // remaining data

// Different parts of the same array with no memory copy
header.Clear(); // clears only the first 128 bytes

stackalloc: Temporary Memory Without Heap Allocation

With stackalloc you can allocate temporary memory on the stack instead of the heap. This memory is cleaned up automatically and is not tracked by the GC (Garbage Collector). It’s very efficient for high-frequency, small-data processing scenarios.


Span<int> numbers = stackalloc int[5];
for (int i = 0; i < numbers.Length; i++)
    numbers[i] = i * 10;

Console.WriteLine(string.Join(", ", numbers.ToArray()));

Note: stackalloc works with Span<T> only and should be used with limited sizes. There’s a risk of stack overflow with large data.


What Is Memory<T>?

Memory<T> is the heap-based, async-safe counterpart of Span<T>. While Span<T> only lives on the stack, Memory<T> can live on the heap and can be safely used in asynchronous scenarios like async/await.


using System;

class Program
{
    static async Task Main()
    {
        byte[] data = new byte[1024];
        Memory<byte> memory = data.AsMemory();

        await ProcessAsync(memory.Slice(100, 200));
    }

    static async Task ProcessAsync(Memory<byte> data)
    {
        await Task.Delay(100); // simulate async work
        var span = data.Span;
        span.Fill(255); // direct memory access
        Console.WriteLine("Segment has been filled.");
    }
}

You can access Span<T> directly via the Span property of Memory<T>.


ReadOnlySpan<T> and ReadOnlyMemory<T>

If you want the data to be read-only, use ReadOnlySpan<T> or ReadOnlyMemory<T>. This gives you copy-free access while preventing modification.


ReadOnlySpan<char> span = "Hello World".AsSpan();
Console.WriteLine(span.Slice(6)); // World

String Processing with Span

Using Span<char>, you can perform string slicing much faster than with Substring(). No new string object is created; you simply view a portion of the original string.


ReadOnlySpan<char> text = "1234-5678-9012-3456";
ReadOnlySpan<char> last4 = text.Slice(text.Length - 4);

Console.WriteLine($"Last 4 digits: {last4.ToString()}");

Span vs ArraySegment

ArraySegment<T> represents a portion of an array but works only with arrays. Span<T>, on the other hand, can work over many data sources such as memory, strings, and pointers. Also, because Span<T> is a ref struct, it reduces GC pressure.


int[] numbers = { 1, 2, 3, 4, 5 };
var segment = new ArraySegment<int>(numbers, 1, 3);
Span<int> slice = numbers.AsSpan(1, 3);

Performance Comparison

The following example shows the difference between classic string slicing and using Span<T>. Substring() creates a new string object, while AsSpan() just views existing memory.


string text = "CSharp Performance Test";

var part1 = text.Substring(7, 10);           // creates a new string
ReadOnlySpan<char> part2 = text.AsSpan(7, 10); // no copy

Console.WriteLine(part1);
Console.WriteLine(part2.ToString());

Especially in high-volume string or byte processing, Span significantly reduces GC pressure.


Things to Watch When Using Span and Memory


Example: Slicing a Byte Array

For example, when parsing a network packet or reading file headers, processing large byte arrays without copying increases performance. The following example processes a data packet by slicing it into header and body.


using System;

class Program
{
    static void Main()
    {
        byte[] packet = new byte[1024];
        new Random().NextBytes(packet);

        Span<byte> span = packet.AsSpan();

        Span<byte> header = span.Slice(0, 128);
        Span<byte> body = span.Slice(128);

        Console.WriteLine($"Header length: {header.Length}");
        Console.WriteLine($"Body length: {body.Length}");
    }
}

TL;DR

  • Span<T>: stack-based, copy-free, fast memory slice. (Short-lived, not usable with async.)
  • Memory<T>: heap-based, async-friendly, safe memory reference.
  • ReadOnlySpan / ReadOnlyMemory: read-only access that preserves data integrity.
  • stackalloc: eliminates GC pressure for small temporary data.
  • Operate on large data without copying to significantly reduce CPU and GC load.

Related Articles

Arrays in C#

Learn arrays in C#, including declaration, indexing, looping through elements, and common array operations with examples.