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
Span<T>lebt auf dem Stack; es kann nicht in async-Methoden oder außerhalb von Lambdas verwendet werden.Memory<T>lebt auf dem Heap; es sollte in asynchronen Szenarien bevorzugt werden.- Die Lebensdauer der zugrunde liegenden Daten muss länger sein als die von
Span(sonst Speicherbeschädigung). stackallocsollte nur für kleine Daten (z. B. < 1 KB) verwendet werden.- Sehr effektiv bei Hochleistungs-Text-, Netzwerk-, JSON- und Binärdatenverarbeitung.
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
Arrays (Felder) in C#
Lernen Sie Arrays in C#, inklusive Deklaration, Indexzugriff, Schleifen und typischer Array-Operationen mit Beispielen.
Das Konzept von Source Generators in C# (C# 9+)
Lernen Sie Source Generators in C#, um Code zur Compile-Zeit zu erzeugen und Performance zu verbessern.
Generische Strukturen in C# (List<T>, Dictionary<TKey,TValue>)
Lernen Sie Generics in C# (List<T>, Dictionary<TKey,TValue>), um typsicheren, wiederverwendbaren Code zu schreiben.
Grundlegende Datentypen in C#
Grundlegende Datentypen in C#: numerisch, textbasiert, logisch, objektorientiert und nullable.
Speicherverwaltung und Garbage Collector in C#
Lernen Sie Speicherverwaltung und Garbage Collector in C#, um Objektlebenszyklen und Speicherbereinigung zu verstehen.
Structs in C# – Unterschiede zu Klassen
Erfahren Sie die Unterschiede zwischen Structs und Klassen in C# in Bezug auf Speicher, Vererbung und Performance.
Unsicherer Code und Zeiger in C#
Lernen Sie unsicheren Code und Zeiger in C#, um mit Speicheradressen und Low-Level-Operationen zu arbeiten.