Potrebbe essere più efficiente per i sistemi in generale eliminare gli stack e utilizzare solo Heap per la gestione della memoria?


14

Mi sembra che tutto ciò che può essere fatto con uno stack possa essere fatto con l'heap, ma non tutto ciò che può essere fatto con l'heap può essere fatto con lo stack. È corretto? Quindi, per semplicità, e anche se perdiamo un po 'di prestazioni con determinati carichi di lavoro, non potrebbe essere meglio scegliere uno standard (ad esempio, l'heap)?

Pensa al compromesso tra modularità e prestazioni. So che non è il modo migliore per descrivere questo scenario, ma in generale sembra che la semplicità di comprensione e progettazione potrebbe essere un'opzione migliore anche se esiste un potenziale per prestazioni migliori.


1
In C e C ++, è necessario deallocare esplicitamente la memoria allocata nell'heap. Questo non è più semplice.
user16764,

Ho usato un'implementazione in C # quando il profilo ha rivelato che gli oggetti dello stack sono stati allocati in un'area simile a un heap con una terribile raccolta dei rifiuti. La mia soluzione? Sposta tutto il possibile (ad es. Variabili di loop, variabili temporanee, ecc.) Nella memoria heap persistente. Ha fatto sì che il programma consumasse 10 volte la RAM e funzionasse a 10 volte la velocità.
imallett,

@IanMallett: non capisco la tua spiegazione del problema e della soluzione. Hai un link con maggiori informazioni da qualche parte? In genere trovo che l'allocazione basata su stack sia più veloce.
Frank Hileman,

@FrankHileman il problema di base era questo: l'implementazione di C # che stavo usando aveva una velocità di raccolta dei rifiuti estremamente scarsa. La "soluzione" consisteva nel rendere persistenti tutte le variabili in modo che durante l'esecuzione non si verificassero operazioni di memoria. Ho scritto un pezzo d'opinione qualche tempo fa sullo sviluppo di C # / XNA in generale, che discute anche del contesto.
imallett,

@IanMallett: grazie. Come ex sviluppatore di C / C ++ che attualmente utilizza principalmente C #, la mia esperienza è stata molto diversa. Trovo che le biblioteche siano il problema più grande. Sembra che la piattaforma XBox360 sia stata cotta per gli sviluppatori .net. Di solito quando ho problemi GC, passo al pooling. Aiuta.
Frank Hileman,

Risposte:


30

Gli heap sono dannosi per l'allocazione e la deallocazione della memoria veloci. Se si desidera acquisire molte piccole quantità di memoria per una durata limitata, un heap non è la scelta migliore. Uno stack, con il suo semplicissimo algoritmo di allocazione / deallocazione, eccelle naturalmente in questo (ancora di più se è integrato nell'hardware), motivo per cui le persone lo usano per cose come passare argomenti a funzioni e archiviare variabili locali - il più importante svantaggio è che ha uno spazio limitato e quindi contenere oggetti di grandi dimensioni o cercare di usarlo per oggetti di lunga durata sono entrambe cattive idee.

Sbarazzarsi completamente dello stack per semplificare un linguaggio di programmazione è il modo sbagliato IMO: un approccio migliore sarebbe quello di sottrarre le differenze, lasciare che il compilatore capisca quale tipo di archiviazione usare, mentre il programmatore mette insieme più in alto- costrutti di livello più vicini al modo in cui gli umani pensano - e in effetti linguaggi di alto livello come C #, Java, Python ecc. fanno esattamente questo. Offrono una sintassi quasi identica per oggetti allocati in heap e primitive allocate in stack ('tipi di riferimento' vs. 'tipi di valore' nel linguaggio .NET), sia completamente trasparenti, sia con alcune differenze funzionali che è necessario comprendere per usare il linguaggio correttamente (ma in realtà non devi sapere come funzionano internamente uno stack e un heap).


2
WOW QUESTO ERA BUONO :) Davvero conciso e informativo per un principiante!
Templare oscuro,

1
Su molte CPU lo stack è gestito in hardware, il che è un problema al di fuori del linguaggio ma gioca un ruolo importante in fase di esecuzione.
Patrick Hughes,

@Patrick Hughes: Sì, ma anche l'heap si trova anche nell'hardware, no?
Templare oscuro,

@Dark Quello che probabilmente Patrick vuole dire è che architetture come x86 hanno registri speciali per gestire lo stack e istruzioni speciali per mettere o rimuovere qualcosa dallo / dallo stack. Questo lo rende abbastanza veloce.
FUZxxl,

3
@Donal Fellows: All true. Ma il punto è che pile e cumuli hanno entrambi i loro punti di forza e di debolezza, e usarli di conseguenza produrrà il codice più efficiente.
martedì

8

In poche parole, uno stack non è un po 'di prestazioni. È centinaia o migliaia di volte più veloce dell'heap. Inoltre, la maggior parte delle macchine moderne ha il supporto hardware per lo stack (come x86) e che la funzionalità hardware, ad esempio lo stack di chiamate, non può essere rimossa.


Cosa intendi quando dici che le macchine moderne hanno il supporto hardware per lo stack? Lo stack stesso è già nell'hardware, non è vero?
Templare oscuro,

1
x86 ha registri e istruzioni speciali per gestire lo stack. x86 non ha supporto per gli heap - tali cose sono create dal sistema operativo.
Pubblicazione del

8

No

L'area dello stack in C ++ è incredibilmente veloce in confronto. Non mi permetto che sviluppatori C ++ esperti siano aperti a disabilitare tale funzionalità.

Con C ++ puoi scegliere e avere il controllo. I progettisti non erano particolarmente propensi a introdurre funzionalità che aggiungessero tempo o spazio di esecuzione significativi.

Esercitare quella scelta

Se vuoi creare una libreria o un programma che richiede che ogni oggetto sia allocato in modo dinamico, puoi farlo con C ++. Si eseguirà relativamente lentamente, ma si potrebbe quindi avere quella "modularità". Per il resto di noi, la modularità è sempre facoltativa, introdurla secondo necessità perché entrambe sono necessarie per implementazioni buone / veloci.

alternative

Esistono altre lingue che richiedono che l'heap venga creato per ogni oggetto; è piuttosto lento, tale da compromettere i progetti (programmi del mondo reale) in un modo peggiore del dover imparare entrambi (IMO).

Entrambi sono importanti e C ++ ti dà il consumo energetico entrambi in modo efficace per ogni scenario. Detto questo, il linguaggio C ++ potrebbe non essere l'ideale per il tuo progetto, se questi fattori nel tuo PO sono importanti per te (ad esempio, leggi su linguaggi di livello superiore).


L'heap ha in realtà la stessa velocità dello stack, ma non ha il supporto hardware specializzato per l'allocazione. D'altra parte, ci sono modi per accelerare molto i cumuli (soggetti a una serie di condizioni che li rendono tecniche per soli esperti).
Donal Fellows il

@DonalFellows: il supporto hardware per gli stack è irrilevante. Ciò che è importante è sapere che ogni volta che viene rilasciato qualcosa si può rilasciare qualcosa assegnato dopo di esso. Alcuni linguaggi di programmazione non hanno cumuli che possono liberare oggetti indipendentemente, ma invece hanno solo un metodo "libera tutto allocato dopo".
supercat

6

Quindi, per semplicità, e anche se perdiamo un po 'di prestazioni con determinati carichi di lavoro, non potrebbe essere meglio scegliere uno standard (ad esempio, l'heap)?

In realtà il successo delle prestazioni è probabilmente notevole!

Come altri hanno sottolineato, gli stack sono una struttura estremamente efficiente per la gestione dei dati che obbedisce alle regole LIFO (last in first out). L'allocazione / liberazione della memoria nello stack di solito è solo una modifica a un registro sulla CPU. La modifica di un registro è quasi sempre una delle operazioni più veloci che un processore può eseguire.

L'heap è in genere una struttura di dati abbastanza complessa e l'allocazione / liberazione della memoria richiederà molte istruzioni per eseguire tutta la contabilità associata. Ancora peggio, nelle implementazioni comuni, ogni chiamata a lavorare con l'heap ha il potenziale per provocare una chiamata al sistema operativo. Le chiamate al sistema operativo richiedono molto tempo! Il programma in genere deve passare dalla modalità utente alla modalità kernel e ogni volta che ciò accade, il sistema operativo può decidere che altri programmi hanno esigenze più urgenti e che il programma dovrà attendere.


5

Simula usava il mucchio per tutto. Mettere tutto sull'heap induce sempre un ulteriore livello di riferimento indiretto per le variabili locali e mette ulteriore pressione sul Garbage Collector (devi tenere conto del fatto che Garbage Collector ha davvero risucchiato allora). Questo è in parte il motivo per cui Bjarne ha inventato il C ++.


Quindi in pratica C ++ usa solo l'heap?
Templare oscuro,

2
@ Dark: cosa? No. La mancanza di stack in Simula è stata una fonte d'ispirazione per farlo meglio.
fredoverflow,

Ah capisco cosa intendi adesso! Grazie +1 :)
Templare oscuro,

3

Gli stack sono estremamente efficienti per i dati LIFO, ad esempio i metadati associati alle chiamate di funzione. Lo stack sfrutta anche le funzionalità di progettazione intrinseche della CPU. Poiché le prestazioni a questo livello sono fondamentali praticamente per qualsiasi altra cosa in un processo, prendere quel "piccolo" successo a quel livello si propagherà molto ampiamente. Inoltre, la memoria heap è mobile dal sistema operativo, il che sarebbe mortale per gli stack. Mentre uno stack può essere implementato nell'heap, richiede un overhead che influenzerà letteralmente ogni parte di un processo al livello più granulare.


2

"efficiente" in termini di scrittura del codice forse, ma certamente non in termini di efficienza del software. Le allocazioni dello stack sono essenzialmente gratuite (bastano poche istruzioni per spostare il puntatore dello stack e riservare spazio nello stack per le variabili locali).

Poiché l'allocazione dello stack non richiede quasi tempo, un'allocazione anche su un heap molto efficiente sarà 100k (se non 1M +) volte più lenta.

Ora immagina quante variabili locali e altre strutture di dati utilizza un'applicazione tipica. Ogni piccola "i" che usi come contatore di loop viene allocata un milione di volte più lentamente.

Sicuro se l'hardware è abbastanza veloce, potresti scrivere un'applicazione che usa solo heap. Ma ora immagina che tipo di applicazione potresti scrivere se hai sfruttato l'heap e utilizzato lo stesso hardware.


Quando dici "immagina quante variabili locali e altre strutture di dati utilizza un'applicazione tipica" a quali altre strutture di dati ti riferisci in modo specifico?
Templare oscuro,

1
I valori "100k" e "1M +" sono in qualche modo scientifici? O è solo un modo per dire "molto"?
Bruno Reis,

@Bruno - IMHO i numeri 100K e 1M che ho usato sono in realtà stime prudenti per dimostrare un punto. Se si ha familiarità con VS e C ++, scrivere un programma che alloca 100 byte nello stack e scrivere uno che alloca 100 byte sull'heap. Quindi passare alla vista di disassemblaggio e contare semplicemente il numero di istruzioni di assemblaggio richieste da ogni allocazione. Le operazioni di heap sono in genere diverse chiamate di funzione nella DLL di Windows, ci sono bucket ed elenchi collegati e poi ci sono coalescenza e altri algoritmi. Con lo stack, può essere ridotto a un'istruzione di assemblaggio "aggiungi esp, 100" ...
DXM

2
"100k (se non 1M +) di volte più lento"? È un po 'esagerato. Lascia che sia due ordini di grandezza più lenti, forse tre, ma è tutto. Almeno, il mio Linux è in grado di eseguire allocazioni di heap di 100 milioni (+ alcune istruzioni circostanti) in meno di 6 secondi su un core i5, che non può essere più di qualche centinaio di istruzioni per allocazione - in realtà, è quasi certamente inferiore. Se sei ordini di grandezza più lenti dello stack, c'è qualcosa di gravemente sbagliato nell'implementazione dell'heap del sistema operativo. Sicuramente ci sono molte cose che non
vanno in

1
i moderatori probabilmente stanno per uccidere l'intero thread di commenti. Quindi ecco l'accordo, ammetto che i numeri reali sono stati estratti dal mio ...., ma concordiamo che il fattore è davvero, davvero grande e non fare altri commenti :)
DXM

2

Potresti essere interessato a "Garbage Collection è veloce, ma uno stack è più veloce".

http://dspace.mit.edu/bitstream/handle/1721.1/6622/AIM-1462.ps.Z

Se l'ho letto correttamente, questi ragazzi hanno modificato un compilatore C per allocare "stack frame" sull'heap, quindi usano la garbage collection per de-allocare i frame invece di far scoppiare lo stack.

Gli "stack frame" allocati nello stack superano decisamente i "frame stack" allocati nello heap.


1

Come funzionerà lo stack di chiamate su un heap? In sostanza, dovresti allocare uno stack sull'heap in ogni programma, quindi perché non far sì che l'hardware OS + lo faccia per te?

Se vuoi che le cose siano davvero semplici ed efficienti, dai semplicemente all'utente il suo pezzo di memoria e lascia che se ne occupino. Naturalmente, nessuno vuole implementare tutto da solo ed è per questo che abbiamo uno stack e un heap.


A rigor di termini uno "stack di chiamate" non è una caratteristica richiesta di un ambiente di runtime del linguaggio di programmazione. ad es. un'implementazione diretta di un linguaggio funzionale valutato pigramente mediante riduzione del grafico (che ho codificato) non ha stack di chiamate. Ma lo stack di chiamate è una tecnica molto utile e ampiamente utilizzata, soprattutto perché i processori moderni presumono che tu lo usi e sono ottimizzati per il suo utilizzo.
Ben

@Ben - mentre è vero (e una buona cosa) astrarre cose come l'allocazione della memoria da un linguaggio, questo non cambia l'architettura del computer attualmente prevalente. Pertanto, il codice di riduzione del grafico utilizzerà comunque lo stack durante l'esecuzione, che piaccia o no.
Ingo,

@Ingo Non proprio in senso significativo. Certo, il sistema operativo inizializzerà una sezione di memoria tradizionalmente chiamata "stack", e ci sarà un registro che lo punta. Ma le funzioni nella lingua di origine non vengono rappresentate come frame di stack in ordine di chiamata. L'esecuzione della funzione è interamente rappresentata dalla manipolazione delle strutture dati nell'heap. Anche senza utilizzare l'ottimizzazione dell'ultima chiamata non è possibile "overflow dello stack". Questo è ciò che intendo quando dico che non c'è nulla di fondamentale nello "stack di chiamate".
Ben

Non parlo delle funzioni del linguaggio di origine ma delle funzioni dell'interprete (o di qualsiasi altra cosa) che effettivamente eseguono la riduzione del grafico. Quelli avranno bisogno di uno stack. Ciò è evidente, poiché l'hardware contemporaneo non riduce il grafico. Quindi, il tuo algoritmo di riduzione del grafico è in definitiva mappato sull'ode macchina, e scommetto che ci sono chiamate di subroutine tra di loro. QED.
Ingo,

1

Sono richiesti sia stack che heap. Sono utilizzati in diverse situazioni, ad esempio:

  1. L'allocazione dell'heap ha una limitazione che sizeof (a [0]) == sizeof (a [1])
  2. L'allocazione dello stack ha una limitazione che sizeof (a) è costante di compilazione
  3. L'allocazione dell'heap può fare cicli, grafici ecc. Strutture dati complesse
  4. L'allocazione dello stack può eseguire alberi di dimensioni in fase di compilazione
  5. L'heap richiede il monitoraggio della proprietà
  6. L'allocazione e la deallocazione dello stack sono automatiche
  7. La memoria heap può essere facilmente trasferita da un ambito all'altro tramite puntatori
  8. La memoria dello stack è locale per ogni funzione e gli oggetti devono essere spostati nell'ambito superiore per prolungarne la durata (o archiviati all'interno di oggetti anziché all'interno di funzioni membro)
  9. L'heap è negativo per le prestazioni
  10. Lo stack è piuttosto veloce
  11. Gli oggetti heap vengono restituiti dalle funzioni tramite puntatori che diventano proprietari. O shared_ptrs.
  12. Gli oggetti dello stack vengono restituiti dalle funzioni tramite riferimenti che non diventano proprietari.
  13. Heap richiede la corrispondenza di ogni nuovo con il tipo corretto di eliminazione o eliminazione []
  14. Gli oggetti stack utilizzano RAII e gli elenchi di inizializzazione del costruttore
  15. Gli oggetti heap possono essere inizializzati in qualsiasi punto all'interno di una funzione e non possono utilizzare i parametri del costruttore
  16. Gli oggetti stack utilizzano i parametri del costruttore per l'inizializzazione
  17. Heap utilizza array e le dimensioni dell'array possono cambiare in fase di esecuzione
  18. Lo stack è per singoli oggetti e la dimensione è fissa in fase di compilazione

Fondamentalmente i meccanismi non possono essere confrontati affatto perché molti dettagli sono diversi. L'unica cosa comune con loro è che entrambi gestiscono la memoria in qualche modo.


1

I computer moderni hanno diversi strati di memoria cache oltre a un sistema di memoria principale grande ma lento. Si possono fare dozzine di accessi alla memoria cache più veloce nel tempo richiesto per leggere o scrivere un byte dal sistema di memoria principale. Pertanto, l'accesso a una posizione mille volte è molto più veloce dell'accesso a 1.000 (o anche 100) posizioni indipendenti una volta ciascuna. Poiché la maggior parte delle applicazioni assegna e distribuisce ripetutamente piccole quantità di memoria vicino alla parte superiore dello stack, le posizioni nella parte superiore dello stack vengono utilizzate e riutilizzate in quantità enorme, in modo tale che la stragrande maggioranza (99% + in un'applicazione tipica) degli accessi allo stack può essere gestito utilizzando la memoria cache.

Al contrario, se un'applicazione dovesse creare e abbandonare ripetutamente oggetti heap per archiviare informazioni di continuazione, ogni versione di ogni oggetto stack che sarebbe mai stata creata dovrebbe essere scritta nella memoria principale. Anche se la stragrande maggioranza di tali oggetti sarebbe completamente inutile nel momento in cui la CPU volesse riciclare le pagine della cache in cui sono state avviate, la CPU non avrebbe modo di saperlo. Di conseguenza, la CPU dovrebbe perdere molto tempo eseguendo scritture di memoria lente di informazioni inutili. Non è esattamente una ricetta per la velocità.

Un'altra cosa da considerare è che in molti casi è utile sapere che un riferimento a un oggetto passato a una routine non verrà utilizzato una volta terminata la routine. Se i parametri e le variabili locali vengono passati tramite lo stack e se l'ispezione del codice della routine rivela che non persiste una copia del riferimento passato, allora il codice che chiama la routine può essere sicuro che se nessun riferimento esterno al l'oggetto esisteva prima della chiamata, nessuno ne esisterà successivamente. Al contrario, se i parametri fossero passati tramite oggetti heap, concetti come "dopo un ritorno di routine" diventano un po 'più nebulosi, poiché se il codice mantenesse una copia della continuazione, sarebbe possibile che la routine "restituisca" più di una volta seguendo un chiamata singola.

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.