Freigeben über


Reduzieren der Speicherzuweisungen mit neuen C#-Features

Von Bedeutung

Die in diesem Abschnitt beschriebenen Techniken verbessern die Leistung, wenn sie auf heiße Pfade in Ihrem Code angewendet wird. Hot paths sind diejenigen Abschnitte deines Codebestands, die in normalen Abläufen häufig und wiederholt ausgeführt werden. Das Anwenden dieser Techniken auf Code, der nicht häufig ausgeführt wird, hat minimale Auswirkungen. Bevor Sie Änderungen vornehmen, um die Leistung zu verbessern, ist es entscheidend, einen Ausgangswert zu messen. Analysieren Sie dann diese Baseline, um zu bestimmen, wo Speicherengpässe auftreten. Im Abschnitt " Diagnose und Instrumentierung" erfahren Sie mehr über viele plattformübergreifende Tools, um die Leistung Ihrer Anwendung zu messen. Sie können eine Profilerstellungssitzung im Lernprogramm üben, um die Speicherauslastung in der Visual Studio-Dokumentation zu messen.

Nachdem Sie die Speicherauslastung gemessen und festgestellt haben, dass Sie Zuordnungen reduzieren können, verwenden Sie die Techniken in diesem Abschnitt, um Zuordnungen zu reduzieren. Messen Sie nach jeder nachfolgenden Änderung die Speicherauslastung erneut. Stellen Sie sicher, dass jede Änderung einen positiven Einfluss auf die Speicherauslastung in Ihrer Anwendung hat.

Leistungsarbeit in .NET bedeutet häufig das Entfernen von Zuordnungen aus Ihrem Code. Jeder Speicherblock, den Sie zuweisen, muss schließlich freigegeben werden. Weniger Zuweisungen reduzieren den Zeitaufwand für die Garbage Collection. Es bietet die Möglichkeit, die Ausführungszeit besser vorherzusagen, indem Garbage Collections aus bestimmten Code-Pfaden entfernt werden.

Eine häufige Taktik zur Reduzierung von Allokationen besteht darin, kritische Datenstrukturen von class Typen in struct Typen zu ändern. Diese Änderung wirkt sich auf die Semantik der Verwendung dieser Typen aus. Parameter und Rückgaben werden jetzt als Wert statt als Referenz übergeben. Die Kosten für das Kopieren eines Werts sind vernachlässigbar, wenn die Typen klein, drei Wörter oder weniger sind (wenn man berücksichtigt, dass ein Wort von einer natürlichen Größe einer ganzen Zahl ist). Es ist messbar und kann echte Auswirkungen auf die Leistung für größere Typen haben. Um den Effekt des Kopierens zu bekämpfen, können Entwickler diese Typen durch ref übergeben, um die beabsichtigte Semantik zurückzuerlangen.

Die C# ref -Features bieten Ihnen die Möglichkeit, die gewünschte Semantik für struct Typen auszudrücken, ohne sich negativ auf ihre allgemeine Benutzerfreundlichkeit zu auswirken. Vor diesen Verbesserungen mussten Entwickler auf Konstrukte mit Zeigern und unformatiertem Speicher zurückgreifen unsafe , um die gleichen Leistungseinbußen zu erzielen. Der Compiler generiert nachweisbar sicheren Code für die neuen ref zugehörigen Features. Sicherer Code bedeutet, dass der Compiler mögliche Pufferüberläufe erkennt oder auf nicht zugewiesenen oder freigegebenen Arbeitsspeicher zugreift. Der Compiler erkennt und verhindert einige Fehler.

Übergabe und Rückgabe per Referenz

Variablen in C# speichern Werte. In struct Typen ist der Wert der Inhalt einer Instanz des Typs. In class Typen ist der Wert ein Verweis auf einen Speicherblock, der eine Instanz des Typs speichert. Das Hinzufügen des ref Modifizierers bedeutet, dass die Variable den Verweis auf den Wert speichert. In struct Typen verweist der Verweis auf den Speicher, der den Wert enthält. Bei class-Typen verweist die Referenz auf den Storage, der die Referenz auf den Block des Speichers enthält.

In C# werden Parameter für Methoden nach Wert übergeben, und Rückgabewerte werden nach Wert zurückgegeben. Der Wert des Arguments wird an die Methode übergeben. Der Wert des Rückgabearguments ist der Rückgabewert.

Der ref, in, ref readonly, oder out Modifizierer gibt an, dass das Argument durch Verweis übergeben wird. Ein Verweis auf den Speicherort wird an die Methode übergeben. Das Hinzufügen ref zur Methodensignatur bedeutet, dass der Rückgabewert per Verweis zurückgegeben wird. Ein Verweis auf den Speicherort ist der Rückgabewert.

Sie können auch die Verweiszuweisung verwenden, um eine Variable auf eine andere Variable verweisen zu lassen. Eine typische Zuweisung kopiert den Wert der rechten Seite in die Variable auf der linken Seite der Zuweisung. Eine Verweiszuweisung kopiert die Speicherposition der Variablen auf der rechten Seite auf die Variable auf der linken Seite. Das ref jetzt bezieht sich auf die ursprüngliche Variable:

int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment

Console.WriteLine(location); // output: 42

sameLocation = 19; // assignment

Console.WriteLine(anInteger); // output: 19

Wenn Sie eine Variable zuweisen , ändern Sie den Wert. Wenn Sie einer Variablen Referenzzuweisung geben, ändern Sie die Referenz der Variablen.

Sie können direkt mit dem Storage für Werte arbeiten, indem Sie ref Variablen, pass by reference und ref Zuweisung verwenden. Vom Compiler erzwungene Bereichsregeln sorgen für Sicherheit beim direkten Arbeiten mit Speicher.

Die ref readonly und in Modifizierer geben an, dass das Argument per Referenz übergeben und in der Methode nicht neu zugewiesen werden darf. Der Unterschied besteht darin, dass die Methode ref readonly den Parameter als Variable verwendet. Die Methode kann den Parameter erfassen oder den Parameter durch eine schreibgeschützte Referenz zurückgeben. In diesen Fällen sollten Sie den ref readonly Modifizierer verwenden. Andernfalls bietet der in Modifizierer mehr Flexibilität. Sie müssen den in Modifizierer nicht zu einem Argument für einen in Parameter hinzufügen, sodass Sie vorhandene API-Signaturen mithilfe des in Modifizierers sicher aktualisieren können. Der Compiler gibt eine Warnung aus, wenn Sie nicht entweder den ref- oder in-Modifizierer zu einem Argument für einen ref readonly-Parameter hinzufügen.

Ref sicherer Kontext

C# enthält Regeln für ref Ausdrücke, um sicherzustellen, dass auf einen ref Ausdruck nicht zugegriffen werden kann, wenn der Speicher, auf den er verweist, nicht mehr gültig ist. Betrachten Sie das folgenden Beispiel:

public ref int CantEscape()
{
    int index = 42;
    return ref index; // Error: index's ref safe context is the body of CantEscape
}

Der Compiler meldet einen Fehler, da Sie keinen Verweis auf eine lokale Variable aus einer Methode zurückgeben können. Der Aufrufer kann nicht auf den Speicher zugreifen, auf den verwiesen wird. Der ref-sichere Kontext definiert den Umfang, in dem ein ref Ausdruck sicher zugegriffen oder geändert werden kann. In der folgenden Tabelle sind die referenzsicheren Kontexte für Variablentypen aufgeführt. ref Felder können nicht in einem class oder einer Nicht-Referenz struct deklariert werden, daher sind diese Zeilen nicht in der Tabelle enthalten.

Erklärung Referenzsicherer Kontext
nicht-ref lokal Block, in dem eine lokale Variable deklariert wird
nicht-ref Parameter aktuelle Methode
ref, ref readonly, in Parameter aufrufende Methode
out-Parameter aktuelle Methode
class-Feld aufrufende Methode
nicht-ref struct Feld aktuelle Methode
ref Feld von ref struct aufrufende Methode

Eine Variable kann refzurückgegeben werden, wenn ihr ref sicherer Kontext die aufrufende Methode ist. Wenn ihr ref sicherer Kontext die aktuelle Methode oder eine Blockierung ist, ref ist die Rückgabe nicht erlaubt. Der folgende Codeausschnitt zeigt zwei Beispiele. Auf ein Mitgliedsfeld kann aus dem Bereich, der eine Methode aufruft, zugegriffen werden. Der ref safe context eines Klassen- oder Struct-Feldes ist also die aufrufende Methode. Der ref sichere Kontext für einen Parameter mit den ref oder in Modifikatoren ist die gesamte Methode. Beide können ref von einer Mitglied-Methode zurückgegeben werden:

private int anIndex;

public ref int RetrieveIndexRef()
{
    return ref anIndex;
}

public ref int RefMin(ref int left, ref int right)
{
    if (left < right)
        return ref left;
    else
        return ref right;
}

Hinweis

Wenn der ref readonly- oder in-Modifizierer auf einen Parameter angewendet wird, kann dieser Parameter von ref readonly und nicht von ref zurückgegeben werden.

Der Compiler stellt sicher, dass ein Verweis dem Ref-sicheren Kontext nicht entkommen kann. Sie können ref Parameter, ref return und ref lokale Variablen sicher verwenden, weil der Compiler erkennt, ob Sie versehentlich Code geschrieben haben, auf den ein ref Ausdruck zugegriffen werden kann, wenn der Speicher ungültig ist.

Sicherer Kontext und Verweisstruktur

ref struct Typen erfordern weitere Regeln, um sicherzustellen, dass sie sicher verwendet werden können. Ein ref struct-Typ kann ref-Felder enthalten. Dies erfordert die Einführung eines sicheren Kontexts. Für die meisten Typen ist der sichere Kontext die aufrufende Methode. Mit anderen Worten, ein Wert, der kein ref struct Wert ist, kann immer von einer Methode zurückgegeben werden.

Informell ist der sichere Kontext für ein ref struct der Bereich, in dem auf alle seine ref Felder zugegriffen werden kann. Mit anderen Worten, es ist die Schnittmenge der ref sichere Kontext aller ref Felder. Die folgende Methode gibt ein ReadOnlySpan<char> an ein Mitgliedsfeld zurück, so dass ihr sicherer Kontext die Methode ist:

private string longMessage = "This is a long message";

public ReadOnlySpan<char> Safe()
{
    var span = longMessage.AsSpan();
    return span;
}

Im Gegensatz dazu gibt der folgende Code einen Fehler aus, weil sich das ref field Mitglied des Span<int> auf das vom Stack zugewiesene Array von Ganzzahlen bezieht. Er kann die Methode nicht verlassen:

public Span<int> M()
{
    int length = 3;
    Span<int> numbers = stackalloc int[length];
    for (var i = 0; i < length; i++)
    {
        numbers[i] = i;
    }
    return numbers; // Error! numbers can't escape this method.
}

Vereinheitlichen von Speichertypen

Die Einführung von System.Span<T> und System.Memory<T> bietet ein einheitliches Modell für die Arbeit mit Arbeitsspeicher. System.ReadOnlySpan<T> und System.ReadOnlyMemory<T> bieten schreibgeschützte Versionen für den Zugriff auf den Speicher. Sie alle bieten eine Abstraktion über einen Speicherblock, der ein Array ähnlicher Elemente speichert. Der Unterschied besteht darin, dass Span<T> und ReadOnlySpan<T>ref struct Typen sind, wobei Memory<T> und ReadOnlyMemory<T>struct Typen sind. Spans enthalten ein ref field. Daher können Instanzen einer Spanne ihren sicheren Kontext nicht verlassen. Der sichere Kontext eines ref struct ist der sichere Ref-Kontext seines ref field. Die Implementierung von Memory<T> und ReadOnlyMemory<T> entfernt diese Einschränkung. Sie verwenden diese Typen, um direkt auf Speicherpuffer zuzugreifen.

Verbessern Sie die Leistung mit ref-Sicherheit

Die Verwendung dieser Features zur Leistungsverbesserung umfasst die folgenden Aufgaben:

  • Vermeiden Sie Zuordnungen: Wenn Sie einen Typ von einem class in eins structändern, ändern Sie, wie er gespeichert wird. Lokale Variablen werden im Stapel gespeichert. Mitglieder werden inline gespeichert, wenn das Container-Objekt alloziert wird. Diese Änderung bedeutet weniger Zuordnungen und verringert die Arbeit, die der Garbage Collector ausführt. Es kann auch den Speicherdruck verringern, so dass der Garbage Collector weniger oft ausgeführt wird.
  • Referenzsemantik beibehalten: Das Ändern eines Typs von einem class zu einem struct verändert die Semantik beim Übergeben einer Variablen an eine Methode. Code, der den Status seiner Parameter geändert hat, muss geändert werden. Nachdem der Parameter ein structist, ändert die Methode eine Kopie des ursprünglichen Objekts. Sie können die ursprüngliche Semantik wiederherstellen, indem Sie diesen Parameter als ref Parameter übergeben. Nach dieser Änderung ändert die Methode das Original struct erneut.
  • Vermeiden Sie das Kopieren von Daten: Das Kopieren größerer struct Typen kann sich auf die Leistung in einigen Codepfaden auswirken. Sie können auch den ref-Modifikator hinzufügen, um größere Datenstrukturen per Referenz statt per Wert an Methoden zu übergeben.
  • Einschränken von Änderungen: Wenn ein struct Typ per Verweis übergeben wird, kann die aufgerufene Methode den Zustand der Struktur ändern. Sie können den ref Modifizierer durch die ref readonly Oder in Modifizierer ersetzen, um anzugeben, dass das Argument nicht geändert werden kann. Bevorzugen Sie ref readonly, wenn die Methode den Parameter erfasst oder als Read-Only-Referenz zurückgibt. Sie können auch readonly struct Typen oder struct Typen mit readonly Mitgliedern erstellen, um mehr Kontrolle darüber zu bieten, welche Mitglieder einer struct geändert werden können.
  • Speicher direkt bearbeiten: Einige Algorithmen sind beim Behandeln von Datenstrukturen als Speicherblock mit einer Abfolge von Elementen am effizientesten. Die Typen Span und Memory bieten sicheren Zugriff auf Speicherblöcke.

Keine dieser Techniken erfordert unsafe Code. Mit Bedacht können Sie Leistungsmerkmale aus sicherem Code abrufen, der zuvor nur mithilfe unsicherer Techniken möglich war. Sie können die Techniken im Tutorial über Reduzierung von Speicherzuweisungen selbst ausprobieren.