Perché la dimensione dello stack in C # è esattamente 1 MB?


102

I PC odierni hanno una grande quantità di RAM fisica, ma la dimensione dello stack di C # è solo 1 MB per i processi a 32 bit e 4 MB per i processi a 64 bit ( capacità dello stack in C # ).

Perché la dimensione dello stack in CLR è ancora così limitata?

E perché è esattamente 1 MB (4 MB) (e non 2 MB o 512 KB)? Perché si è deciso di utilizzare questi importi?

Sono interessato a considerazioni e ragioni alla base di tale decisione .


6
La dimensione predefinita dello stack per i processi a 64 bit è 4 MB, è 1 MB per i processi a 32 bit. È possibile modificare la dimensione dello stack dei thread principali modificando il valore nell'intestazione PE. È inoltre possibile specificare la dimensione dello stack utilizzando l'overload corretto del Threadcostruttore. MA, questo solleva la domanda, perché hai bisogno di uno stack più grande?
Yuval Itzchakov

2
Grazie, modificato. :) La domanda non riguarda come usare una dimensione dello stack più grande, ma perché la dimensione dello stack è stata decisa a 1 MB (4 MB) .
Nikolay Kostov

8
Perché ogni thread avrà questa dimensione dello stack per impostazione predefinita e la maggior parte dei thread non ne avrà bisogno. Ho appena avviato il mio PC e il sistema attualmente esegue 1200 thread. Ora fai i
conti

2
@LucasTrzesniewski Non solo, deve essere contagioso nella memoria . Notare che maggiore è la dimensione dello stack, minore è il numero di thread che il processo può creare nel suo spazio degli indirizzi virtuali.
Yuval Itzchakov

Non sono sicuro di "esattamente" 1 MB: sul mio Windows 8.1, un'applicazione console .NET Core 3.1 ha 1572864una dimensione dello stack predefinita in byte (recuperata utilizzando l'API Win32 GetCurrentThreadStackLimits). Sono in grado di stackalloccirca 1500000byte senza StackOverflowException.
George Chakhidze,

Risposte:


210

inserisci qui la descrizione dell'immagine

Stai guardando il ragazzo che ha fatto quella scelta. David Cutler e il suo team hanno selezionato un megabyte come dimensione dello stack predefinita. Niente a che vedere con .NET o C #, questo è stato risolto quando hanno creato Windows NT. Un megabyte è ciò che sceglie quando l'intestazione EXE di un programma o la chiamata winapi CreateThread () non specifica esplicitamente la dimensione dello stack. Che è il modo normale, quasi tutti i programmatori lasciano che sia il sistema operativo a scegliere la dimensione.

Questa scelta probabilmente è precedente al design di Windows NT, la storia è troppo oscura su questo. Sarebbe bello se Cutler scrivesse un libro a riguardo, ma non è mai stato uno scrittore. È stato straordinariamente influente sul modo in cui funzionano i computer. Il suo primo progetto di sistema operativo è stato RSX-11M, un sistema operativo a 16 bit per computer DEC (Digital Equipment Corporation). Ha fortemente influenzato il CP / M di Gary Kildall, il primo sistema operativo decente per microprocessori a 8 bit. Che ha fortemente influenzato MS-DOS.

Il suo progetto successivo fu VMS, un sistema operativo per processori a 32 bit con supporto di memoria virtuale. Molto successo. Il suo prossimo è stato cancellato dal DEC nel periodo in cui la società ha iniziato a disintegrarsi, non essendo in grado di competere con hardware per PC a basso costo. Cue Microsoft, gli hanno fatto un'offerta che non poteva rifiutare. Anche molti dei suoi colleghi si sono uniti. Hanno lavorato su VMS v2, meglio conosciuto come Windows NT. Il DEC si è arrabbiato, i soldi sono passati di mano per risolverlo. Se VMS ha già scelto un megabyte è qualcosa che non so, conosco solo abbastanza bene RSX-11. Non è improbabile.

Abbastanza storia. Un megabyte è molto , un vero thread raramente consuma più di un paio di manciate di kilobyte. Quindi un megabyte è in realtà piuttosto dispendioso. Tuttavia, è il tipo di spreco che puoi permetterti su un sistema operativo di memoria virtuale con paging a richiesta, quel megabyte è solo memoria virtuale . Solo numeri per il processore, uno ogni 4096 byte. Non usi mai la memoria fisica, la RAM nella macchina, fino a quando non la indirizzi.

È eccessivo in un programma .NET perché la dimensione di un megabyte è stata originariamente scelta per ospitare programmi nativi. Che tendono a creare frame di stack di grandi dimensioni, memorizzando anche stringhe e buffer (array) nello stack. Famoso per essere un vettore di attacco malware, un buffer overflow può manipolare il programma con i dati. Non è il modo in cui funzionano i programmi .NET, le stringhe e gli array vengono allocati sull'heap GC e l'indicizzazione viene controllata. L'unico modo per allocare spazio nello stack con C # è con la parola chiave stackalloc non sicura .

L'unico utilizzo non banale dello stack in .NET è il jitter. Usa lo stack del tuo thread per compilare MSIL just-in-time in codice macchina. Non ho mai visto o controllato quanto spazio richiede, piuttosto dipende dalla natura del codice e se l'ottimizzatore è abilitato o meno, ma un paio di decine di kilobyte è un'ipotesi approssimativa. Che è altrimenti il ​​modo in cui questo sito web ha preso il nome, un overflow dello stack in un programma .NET è abbastanza fatale. Non c'è abbastanza spazio rimasto (meno di 3 kilobyte) per JIT ancora in modo affidabile qualsiasi codice che tenta di catturare l'eccezione. Kaboom to desktop è l'unica opzione.

Ultimo ma non meno importante, un programma .NET fa qualcosa di piuttosto improduttivo con lo stack. Il CLR eseguirà il commit dello stack di un thread. È una parola costosa che significa che non si limita a riservare la dimensione dello stack, ma si assicura anche che lo spazio sia riservato nel file di paging del sistema operativo in modo che lo stack possa sempre essere sostituito quando necessario. L'impossibilità di eseguire il commit è un errore irreversibile e termina un programma incondizionatamente. Ciò accade solo su macchine con poca RAM che esegue troppi processi, una macchina del genere si sarà trasformata in melassa prima che i programmi inizino a morire. Un possibile problema più di 15 anni fa, non oggi. I programmatori che ottimizzano il loro programma per comportarsi come un'auto da corsa di F1 utilizzano l' <disableCommitThreadStack>elemento nel loro file .config.

Fwiw, Cutler non ha smesso di progettare sistemi operativi. Quella foto è stata fatta mentre lavorava su Azure.


Aggiornamento, ho notato che .NET non esegue più il commit dello stack. Non sono esattamente sicuro di quando o perché sia ​​successo, è passato troppo tempo da quando ho controllato. Immagino che questa modifica al design sia avvenuta da qualche parte intorno a .NET 4.5. Cambiamento abbastanza sensato.


3
rispetto al tuo commento The only way to allocate space on the stack with C# is with the unsafe stackalloc keyword.- Le variabili locali, ad esempio una intdichiarazione all'interno di un metodo, non sono memorizzate nello stack? Penso che siano.
RBT

2
Ok. Ora ho capito che uno stack-frame non è l'unica scelta di archiviazione per le variabili locali di una funzione. Si può essere memorizzato su uno stack frame anche se, come suggerito in uno dei vostri punti elenco. Hans molto illuminante. Non posso dire abbastanza grazie per aver scritto post così penetranti. Onestamente lo stack è un'astrazione così grande per la programmazione in generale solo per evitare complessità inutili.
RBT

Descrizione molto dettagliata @Hans. Mi stavo solo chiedendo qual è il valore minimo possibile per maxStackSizeun thread? Non sono riuscito a trovarlo su [MSDN] ( msdn.microsoft.com/en-us/library/5cykbwz4(v=vs.110).aspx ). In base ai tuoi commenti, sembra che l'utilizzo dello stack sia minimo assoluto e posso utilizzare il valore più piccolo per ospitare il massimo di thread possibili. Grazie.
MKR

1
@KFL: potresti rispondere facilmente alla tua domanda provandolo!
Eric Lippert

1
Se il comportamento predefinito è non eseguire più il commit dello stack, questo file
markdown

5

La dimensione dello stack riservato predefinito è specificata dal linker e può essere sovrascritta dagli sviluppatori modificando il valore PE al momento del collegamento o per un singolo thread specificando il dwStackSizeparametro per la CreateThreadfunzione WinAPI.

Se crei un thread con la dimensione dello stack iniziale maggiore o uguale alla dimensione dello stack predefinita, viene arrotondato per eccesso al multiplo più vicino di 1 MB.

Perché il valore è uguale a 1 MB per processi a 32 bit e 4 MB per 64 bit? Penso che dovresti chiedere agli sviluppatori, che hanno progettato Windows, o aspettare che qualcuno di loro risponda alla tua domanda.

Probabilmente Mark Russinovich lo sa e puoi contattarlo . Forse puoi trovare queste informazioni nei suoi libri di Windows Internals precedenti alla sesta edizione che descrivono meno informazioni sugli stack piuttosto che nel suo articolo . O forse Raymond Chen conosce le ragioni da quando scrive cose interessanti sugli interni di Windows e sulla sua storia. Può anche rispondere alla tua domanda, ma dovresti pubblicare un suggerimento nel riquadro dei suggerimenti .

Ma in questo momento cercherò di spiegare alcuni probabili motivi per cui Microsoft ha scelto questi valori utilizzando i blog di MSDN, Mark e Raymond.

Le impostazioni predefinite hanno questi valori probabilmente perché nei primi tempi i PC erano lenti e l'allocazione della memoria nello stack era molto più veloce dell'allocazione della memoria nell'heap. E poiché le allocazioni dello stack erano molto più economiche, sono state utilizzate, ma richiedeva una dimensione dello stack maggiore.

Quindi il valore era la dimensione dello stack riservata ottimale per la maggior parte delle applicazioni. È ottimale perché consente di effettuare molte chiamate annidate e allocare memoria sullo stack per passare strutture alle funzioni chiamanti. Allo stesso tempo permette di creare molti thread.

Oggigiorno questi valori sono usati principalmente per la compatibilità con le versioni precedenti, perché le strutture che vengono passate come parametri alle funzioni WinAPI sono ancora allocate nello stack. Ma se non stai usando le allocazioni dello stack, l'utilizzo dello stack di un thread sarà significativamente inferiore al valore predefinito di 1 MB ed è uno spreco come menzionato da Hans Passant. E per evitare ciò, il sistema operativo esegue il commit solo della prima pagina dello stack (4 KB), se non è specificato altro nell'intestazione PE dell'applicazione. Altre pagine vengono assegnate su richiesta.

Alcune applicazioni ignorano lo spazio degli indirizzi riservato e inizialmente si impegnano a ottimizzare l'utilizzo della memoria. Ad esempio, la dimensione massima dello stack del thread di un processo nativo IIS è 256 KB ( KB932909 ). E questa diminuzione dei valori predefiniti è consigliata da Microsoft:

È meglio scegliere la dimensione dello stack più piccola possibile e impegnare lo stack necessario affinché il thread o la fibra funzionino in modo affidabile. Ogni pagina riservata allo stack non può essere utilizzata per nessun altro scopo.

Fonti:

  1. Dimensione pila filo (Microsoft Docs)
  2. Superare i limiti di Windows: processi e thread (Mark Russinovich)
  3. Per impostazione predefinita, la dimensione massima dello stack di un thread creato in un processo IIS nativo è 256 KB (KB932909)

Se voglio una dimensione dello stack maggiore, posso impostarla ( atalasoft.com/cs/blogs/rickm/archive/2008/04/22/… ). Voglio conoscere le considerazioni e le ragioni alla base di tale decisione.
Nikolay Kostov

2
Va bene. Ora ti capisco :) La dimensione dello stack predefinita dovrebbe essere ottimale (vedi il commento di @Lucas Trzesniewski) e dovrebbe essere arrotondata al multiplo più vicino della granularità di allocazione. Se la dimensione dello stack specificata è maggiore della dimensione dello stack predefinita, viene arrotondata per eccesso al multiplo più vicino di 1 MB. Quindi Microsoft ha scelto queste dimensioni come dimensioni dello stack predefinite per tutte le applicazioni in modalità utente. E non ci sono altre ragioni.
Yoh Deadfall

Qualche fonte? Qualche documentazione? :)
Nikolay Kostov

@Yoh collegamento interessante. Dovresti riassumerlo nella tua risposta.
Lucas Trzesniewski
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.