Praktische Verwendung des Schlüsselworts "stackalloc"

134

Hat jemand jemals tatsächlich stackallocbeim Programmieren in C # verwendet? Ich weiß, was getan wird, aber das einzige Mal, dass es in meinem Code angezeigt wird, ist ein Zufall, da Intellisense dies beispielsweise vorschlägt, wenn ich mit der Eingabe beginne static.

Obwohl es nicht mit den Nutzungsszenarien von zusammenhängt stackalloc, mache ich tatsächlich eine beträchtliche Menge an Legacy-Interop in meinen Apps, sodass ich von Zeit zu Zeit auf die Verwendung von unsafeCode zurückgreifen kann. Trotzdem finde ich normalerweise Wege, dies unsafevollständig zu vermeiden .

Und da die Stapelgröße für einen einzelnen Thread in .Net ~ 1 MB beträgt (korrigieren Sie mich, wenn ich falsch liege), bin ich noch zurückhaltender bei der Verwendung stackalloc.

Gibt es einige praktische Fälle, in denen man sagen könnte: "Dies ist genau die richtige Menge an Daten und Verarbeitung, damit ich unsicher werde und sie verwende stackalloc"?

Groo
quelle
5
Ich habe gerade bemerkt, dass System.Numberses viel verwendet wird referencesource.microsoft.com/#mscorlib/system/…
Slai

Antworten:

150

Der einzige Grund für die Verwendung stackallocist die Leistung (entweder für Berechnungen oder Interop). Wenn Sie stackallocanstelle eines Heap-zugewiesenen Arrays weniger GC-Druck erzeugen (der GC muss weniger ausgeführt werden), müssen Sie die Arrays nicht festnageln. Die Zuweisung ist schneller als bei einem Heap-Array und wird bei der Methode automatisch freigegeben exit (Heap-zugewiesene Arrays werden nur freigegeben, wenn GC ausgeführt wird). Durch die Verwendung stackalloceines nativen Allokators (wie malloc oder des .Net-Äquivalents) erhalten Sie außerdem Geschwindigkeit und automatische Freigabe beim Beenden des Bereichs.

In Bezug auf die Leistung stackallocerhöhen Sie bei Verwendung die Wahrscheinlichkeit von Cache-Treffern auf der CPU aufgrund der Datenlokalität erheblich.

Pop Catalin
quelle
26
Lokalität der Daten, guter Punkt! Dies wird verwalteter Speicher selten erreichen, wenn Sie mehrere Strukturen oder Arrays zuweisen möchten. Vielen Dank!
Groo
22
Heap-Zuweisungen sind für verwaltete Objekte normalerweise schneller als für nicht verwaltete, da keine freie Liste zum Durchlaufen vorhanden ist. Die CLR erhöht nur den Heap-Zeiger. In Bezug auf die Lokalität ist es wahrscheinlicher, dass sequentielle Zuweisungen aufgrund der Heap-Komprimierung für lange laufende verwaltete Prozesse kolokalisiert werden.
Shea
1
"Es ist schneller zuzuweisen als ein Heap-Array" Warum ist das so? Nur Ort? So oder so ist es nur ein Zeigerstoß, nein?
Max Barraclough
2
@MaxBarraclough Da Sie die GC-Kosten zu Heap-Zuordnungen über die Anwendungslebensdauer hinzufügen. Gesamtzuweisungskosten = Zuweisung + Freigabe, in diesem Fall Zeigerstoß + GC-Heap, gegen Zeigerstoß + Zeigerdekrementierungsstapel
Pop Catalin
35

Ich habe Stackalloc verwendet, um Puffer für [fast] Echtzeit-DSP-Arbeit zuzuweisen. Es war ein sehr spezifischer Fall, in dem die Leistung so konstant wie möglich sein musste. Beachten Sie, dass es einen Unterschied zwischen Konsistenz und Gesamtdurchsatz gibt. In diesem Fall ging es mir nicht darum, dass die Heap-Zuweisungen zu langsam sind, sondern nur darum, dass die Garbage Collection zu diesem Zeitpunkt im Programm nicht deterministisch ist. Ich würde es in 99% der Fälle nicht verwenden.

Jim Arnold
quelle
25

stackallocist nur für unsicheren Code relevant. Für verwalteten Code können Sie nicht entscheiden, wo Daten zugeordnet werden sollen. Werttypen werden standardmäßig auf dem Stapel zugewiesen (es sei denn, sie sind Teil eines Referenztyps. In diesem Fall werden sie auf dem Heap zugewiesen). Referenztypen werden auf dem Heap zugewiesen.

Die Standardstapelgröße für eine einfache Vanille-.NET-Anwendung beträgt 1 MB. Sie können dies jedoch im PE-Header ändern. Wenn Sie Threads explizit starten, können Sie über die Konstruktorüberladung auch eine andere Größe festlegen. Für ASP.NET-Anwendungen beträgt die Standardstapelgröße nur 256 KB. Dies ist zu beachten, wenn Sie zwischen den beiden Umgebungen wechseln.

Brian Rasmussen
quelle
Ist es möglich, die Standardstapelgröße in Visual Studio zu ändern?
Konfigurator
@configurator: Soweit mir bekannt ist.
Brian Rasmussen
17

Stackalloc-Initialisierung von Spannen. In früheren Versionen von C # konnte das Ergebnis von stackalloc nur in einer lokalen Zeigervariablen gespeichert werden. Ab C # 7.2 kann Stackalloc jetzt als Teil eines Ausdrucks verwendet werden und auf eine Spanne abzielen. Dies kann ohne Verwendung des unsicheren Schlüsselworts erfolgen. Also anstatt zu schreiben

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

Sie können einfach schreiben:

Span<byte> bytes = stackalloc byte[length];

Dies ist auch in Situationen äußerst nützlich, in denen Sie zum Ausführen eines Vorgangs etwas Arbeitsbereich benötigen, aber die Zuweisung von Heap-Speicher für relativ kleine Größen vermeiden möchten

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Quelle: C # - Alles über Span: Erkunden einer neuen .NET-Hauptstütze

anth
quelle
4
Danke für den Tipp. Es scheint, dass jede neue Version von C # C ++ ein Stück näher kommt, was meiner Meinung nach eine gute Sache ist.
Groo
1
Wie hier und hier zu sehen ist , Spanist es in .NET Framework 4.7.2 und sogar nicht in 4.8 leider nicht verfügbar ... Daher ist diese neue Sprachfunktion im Moment noch von begrenztem Nutzen.
Frederic
2

Diese Frage enthält einige gute Antworten, aber ich möchte nur darauf hinweisen

Stackalloc kann auch zum Aufrufen nativer APIs verwendet werden

Bei vielen nativen Funktionen muss der Aufrufer einen Puffer zuweisen, um das Rückgabeergebnis zu erhalten. Beispielsweise hat die Funktion CfGetPlaceholderInfo in cfapi.hdie folgende Signatur.

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

Um es in C # über Interop aufzurufen,

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

Sie können stackalloc verwenden.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
fjch1997
quelle