Uso pratico della parola chiave `stackalloc`


134

Qualcuno ha mai usato stackallocdurante la programmazione in C #? Sono consapevole di ciò che fa, ma l'unica volta che compare nel mio codice è per caso, perché Intellisense lo suggerisce quando inizio a digitare static, ad esempio.

Sebbene non sia correlato agli scenari di utilizzo di stackalloc, in realtà faccio una notevole quantità di interoperabilità legacy nelle mie app, quindi ogni tanto potrei ricorrere all'uso del unsafecodice. Ma comunque di solito trovo il modo di evitare unsafecompletamente.

E poiché la dimensione dello stack per un singolo thread in .Net è ~ 1Mb (correggimi se sbaglio), sono ancora più riservato dall'uso stackalloc.

Ci sono alcuni casi pratici in cui si potrebbe dire: "questa è esattamente la giusta quantità di dati e di elaborazione per me di non essere sicuro e utilizzare stackalloc"?


5
System.Numbersho appena notato che lo usa molto references.microsoft.com/#mscorlib/system/…
Slai,

Risposte:


150

L'unico motivo da utilizzare stackallocè la prestazione (sia per i calcoli che per l'interoperabilità). Utilizzando stackallocinvece di un array allocato in heap, si crea una pressione del GC inferiore (il GC deve funzionare di meno), non è necessario fissare gli array in basso, è più veloce da allocare rispetto a un array in heap, viene automaticamente liberato il metodo exit (gli array allocati in heap vengono deallocati solo quando GC viene eseguito). Inoltre, utilizzando stackallocinvece un allocatore nativo (come malloc o l'equivalente .Net) si ottiene anche velocità e deallocazione automatica all'uscita dell'ambito.

Per quanto riguarda le prestazioni, se si utilizza stackallocsi aumenta notevolmente la possibilità di hit della cache sulla CPU a causa della localizzazione dei dati.


26
Località dei dati, buon punto! Questo è ciò che la memoria gestita raggiungerà raramente quando si desidera allocare più strutture o array. Grazie!
Groo

22
Le allocazioni di heap sono generalmente più veloci per gli oggetti gestiti che per quelli non gestiti perché non esiste un elenco gratuito da attraversare; il CLR incrementa semplicemente il puntatore dell'heap. Per quanto riguarda la località, le allocazioni sequenziali hanno maggiori probabilità di finire colocate per processi gestiti di lunga durata a causa della compattazione dell'heap.
Shea,

1
"è più veloce da allocare rispetto a un array heap" Perché? Solo località? In entrambi i casi è solo un bump puntatore, no?
Max Barraclough,

2
@MaxBarraclough Perché si aggiunge il costo GC per accumulare allocazioni nel corso della vita dell'applicazione. Costo di allocazione totale = allocazione + deallocazione, in questo caso bump puntatore + Heap GC, vs bump puntatore + decremento puntatore Stack
Pop Catalin

35

Ho usato Stackalloc per allocare i buffer per il lavoro DSP [quasi] in tempo reale. È stato un caso molto specifico in cui le prestazioni dovevano essere il più coerenti possibile. Nota che c'è una differenza tra coerenza e throughput complessivo - in questo caso non mi preoccupavo che le allocazioni di heap fossero troppo lente, solo con il non determinismo della garbage collection a quel punto del programma. Non lo userei nel 99% dei casi.


25

stackallocè rilevante solo per il codice non sicuro. Per il codice gestito non puoi decidere dove allocare i dati. I tipi di valore vengono allocati nello stack per impostazione predefinita (a meno che non facciano parte di un tipo di riferimento, nel qual caso vengono allocati nell'heap). I tipi di riferimento sono allocati nell'heap.

La dimensione dello stack predefinita per un'applicazione .NET vanilla semplice è 1 MB, ma è possibile modificarla nell'intestazione PE. Se inizi i thread in modo esplicito, puoi anche impostare una dimensione diversa tramite il sovraccarico del costruttore. Per le applicazioni ASP.NET la dimensione dello stack predefinita è solo 256 KB, che è qualcosa da tenere a mente se si passa da un ambiente all'altro.


È possibile modificare la dimensione dello stack predefinita da Visual Studio?
configuratore

@configurator: non per quanto ne so.
Brian Rasmussen,

17

Inizializzazione stackalloc degli span. Nelle versioni precedenti di C #, il risultato di stackalloc poteva essere archiviato solo in una variabile locale del puntatore. A partire da C # 7.2, stackalloc può ora essere usato come parte di un'espressione e può scegliere come target un intervallo, e ciò può essere fatto senza usare la parola chiave non sicura. Quindi, invece di scrivere

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

Puoi scrivere semplicemente:

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

Ciò è estremamente utile anche in situazioni in cui è necessario dello spazio per eseguire un'operazione, ma si desidera evitare di allocare memoria heap per dimensioni relativamente ridotte

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

Fonte: C # - All About Span: Exploring a New .NET Mainstay


4
Grazie per il consiglio. Sembra che ogni nuova versione di C # si avvicini un po 'di più a C ++, che in realtà è una buona cosa per IMHO.
Groo

1
Come si può vedere qui e qui , Spanpurtroppo non è disponibile in .NET Framework 4.7.2 e nemmeno in 4.8 ... Quindi la nuova funzionalità del linguaggio è ancora al momento limitata.
Frederic,

2

Ci sono alcune grandi risposte in questa domanda, ma voglio solo sottolinearlo

Stackalloc può anche essere usato per chiamare API native

Molte funzioni native richiedono che il chiamante assegni un buffer per ottenere il risultato di ritorno. Ad esempio, la funzione CfGetPlaceholderInfocfapi.h ha la seguente firma.

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

Per chiamarlo in C # tramite interop,

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

Puoi usare stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.