Wird geladen...

Leistungsoptimierung mit Span<T> und Memory<T> in C#

Lernen Sie Performanceoptimierung in C# mit Span<T> und Memory<T> für effiziente Speicherverwaltung.

Mit C# 7.2 und später eingeführte Span<T> und Memory<T>-Strukturen optimieren den Speicherzugriff in Hochleistungs-Datenverarbeitungsszenarien, indem sie unnötige Kopien vermeiden. Dank dieser Strukturen können Sie mit großen Arrays, Strings oder Byte-Puffern arbeiten, ohne zusätzliche Speicherzuweisungen vorzunehmen.


Was ist Span<T>?

Span<T> ist eine stackbasierte Struktur, die mit dem Konzept des Slice (Datenabschnitts) über den Speicher arbeitet. Sie hält eine Referenz auf einen Teil eines Arrays, Strings oder unmanaged Speichers, ohne Daten zu kopieren. Da sie jedoch auf dem Stack lebt, kann sie nicht in async-Methoden oder Konstrukten verwendet werden, die auf den Heap übertragen werden.


using System;

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

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

        Console.WriteLine(string.Join(", ", zahlen)); 
        // Ausgabe: 10, 99, 30, 40, 50 — verweist auf denselben Speicherbereich
    }
}

Zugriff ohne Kopien mit Slice

Der größte Vorteil von Span<T> ist der Zugriff auf Teilbereiche von Daten ohne Kopieren. Wenn Sie z. B. mit einem bestimmten Teil eines großen Byte-Arrays arbeiten möchten, müssen Sie kein neues Array erstellen.


byte[] buffer = new byte[1000];
Span<byte> header = buffer.AsSpan(0, 128); // Header-Bereich
Span<byte> daten = buffer.AsSpan(128);      // verbleibende Daten

// Verschiedene Bereiche desselben Arrays ohne Speicher-Kopie
header.Clear(); // löscht nur die ersten 128 Bytes

stackalloc: Temporärer Speicher ohne Heap-Allocation

Mit dem Schlüsselwort stackalloc können Sie temporären Speicher auf dem Stack anstelle des Heap zuweisen. Dieser Speicher wird automatisch bereinigt und vom Garbage Collector (GC) nicht verfolgt. Besonders effizient bei häufigen, kleinen Datenverarbeitungsaufgaben.


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

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

Hinweis: stackalloc funktioniert nur mit Span<T> und sollte nur für kleine Datenmengen verwendet werden. Bei großen Daten besteht ein Risiko für Stacküberlauf.


Was ist Memory<T>?

Memory<T> ist die heapbasierte und asynchron sichere Version von Span<T>. Während Span<T> nur auf dem Stack existiert, kann Memory<T> auf dem Heap gespeichert und sicher in asynchronen Szenarien wie async/await verwendet werden.


using System;

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

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

    static async Task VerarbeitenAsync(Memory<byte> data)
    {
        await Task.Delay(100); // asynchrone Simulation
        var span = data.Span;
        span.Fill(255); // direkter Zugriff auf Speicher
        Console.WriteLine("Bereich gefüllt.");
    }
}

Über die Eigenschaft Span von Memory<T> kann direkt auf Span<T> zugegriffen werden.


ReadOnlySpan<T> und ReadOnlyMemory<T>

Wenn die Daten nur lesbar sein sollen, können Sie ReadOnlySpan<T> oder ReadOnlyMemory<T> verwenden. Dadurch wird ein Zugriff ohne Kopieren ermöglicht, aber Änderungen sind nicht erlaubt.


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

String-Verarbeitung mit Span

Mit Span<char> können Sie String-Slices deutlich schneller als mit Substring() ausführen. Es wird kein neues String-Objekt erstellt – es wird einfach auf einen Teil des ursprünglichen Strings verwiesen.


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

Console.WriteLine($"Letzte 4 Ziffern: {letzte4.ToString()}");

Span vs ArraySegment

ArraySegment<T> stellt einen Teil eines Arrays dar, funktioniert jedoch nur mit Arrays. Span<T> hingegen kann über verschiedene Datenquellen wie Speicher, Strings oder Pointer arbeiten. Da Span<T> ein ref struct ist, reduziert es den GC-Druck erheblich.


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

Leistungsvergleich

Das folgende Beispiel zeigt den Unterschied zwischen klassischem String-Slicing und der Verwendung von Span<T>. Substring() erstellt ein neues String-Objekt, während AsSpan() nur den vorhandenen Speicher referenziert.


string text = "CSharp Leistungstest";

var teil1 = text.Substring(7, 10);           // neues String-Objekt
ReadOnlySpan<char> teil2 = text.AsSpan(7, 10); // keine Kopie

Console.WriteLine(teil1);
Console.WriteLine(teil2.ToString());

Besonders bei großen String- oder Byte-Operationen reduziert Span den GC-Druck erheblich.


Wichtige Hinweise bei der Verwendung von Span und Memory


Beispiel: Aufteilen eines Byte-Arrays

Wenn Sie beispielsweise ein Netzwerkpaket analysieren oder Dateiköpfe lesen, verbessert die Verarbeitung großer Byte-Arrays ohne Kopieren die Leistung. Das folgende Beispiel teilt ein Datenpaket in Header und Body auf und verarbeitet es.


using System;

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

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

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

        Console.WriteLine($"Header-Länge: {header.Length}");
        Console.WriteLine($"Inhalt-Länge: {inhalt.Length}");
    }
}

TL;DR

  • Span<T>: stackbasiert, kopierfrei, schneller Speicherabschnitt. (Kurzlebig, nicht mit async verwendbar.)
  • Memory<T>: heapbasiert, async-kompatibel, sichere Speicherreferenz.
  • ReadOnlySpan / ReadOnlyMemory: Nur-Lesezugriff, schützt Datenintegrität.
  • stackalloc: eliminiert GC-Druck bei kleinen temporären Daten.
  • Bearbeiten Sie große Daten ohne Kopien, um CPU- und GC-Last deutlich zu reduzieren.

Ähnliche Artikel