Perché Large Object Heap e perché ci interessa?


105

Ho letto di Generations e Large object heap. Ma non riesco ancora a capire qual è il significato (o il vantaggio) di avere un heap di oggetti di grandi dimensioni?

Cosa sarebbe potuto andare storto (in termini di prestazioni o memoria) se CLR si fosse semplicemente affidato alla seconda generazione (considerando che la soglia per Gen0 e Gen1 è piccola per gestire oggetti di grandi dimensioni) per l'archiviazione di oggetti di grandi dimensioni?


6
Questo mi pone due domande per i progettisti .NET: 1. Perché una deframmentazione LOH non viene chiamata prima che venga generata un'eccezione OutOfMemoryException? 2. Perché non fare in modo che gli oggetti LOH abbiano un'affinità per stare insieme (i grandi preferiscono la fine del mucchio e i piccoli all'inizio)
Jacob Brewer

Risposte:


195

Una garbage collection non si limita a eliminare gli oggetti non referenziati, ma compatta anche l'heap. Questa è un'ottimizzazione molto importante. Non solo rende più efficiente l'utilizzo della memoria (senza buchi inutilizzati), ma rende la cache della CPU molto più efficiente. La cache è davvero un grosso problema sui processori moderni, sono un semplice ordine di grandezza più veloci del bus di memoria.

La compattazione viene eseguita semplicemente copiando i byte. Ciò tuttavia richiede tempo. Più grande è l'oggetto, più è probabile che il costo della copia superi i possibili miglioramenti nell'utilizzo della cache della CPU.

Quindi hanno eseguito una serie di benchmark per determinare il punto di pareggio. Ed è arrivato a 85.000 byte come punto limite in cui la copia non migliora più le prestazioni. Con un'eccezione speciale per gli array di double, sono considerati "grandi" quando l'array ha più di 1000 elementi. Questa è un'altra ottimizzazione per il codice a 32 bit, l'allocatore di heap di oggetti di grandi dimensioni ha la proprietà speciale di allocare memoria agli indirizzi allineati a 8, a differenza del normale allocatore generazionale che alloca solo allineato a 4. Quell'allineamento è un grosso problema per il doppio , leggere o scrivere un doppio disallineato è molto costoso. Stranamente le scarse informazioni di Microsoft non menzionano mai array di lunga durata, non sono sicuro di cosa ci sia.

Fwiw, c'è molta angoscia del programmatore sul fatto che l'heap di oggetti di grandi dimensioni non viene compresso. Ciò viene invariabilmente attivato quando scrivono programmi che consumano più della metà dell'intero spazio di indirizzi disponibile. Seguito utilizzando uno strumento come un profiler di memoria per scoprire perché il programma è stato bombardato anche se c'era ancora molta memoria virtuale inutilizzata disponibile. Un tale strumento mostra i buchi nel LOH, blocchi di memoria inutilizzati in cui in precedenza viveva un oggetto di grandi dimensioni ma veniva raccolto dalla spazzatura. Tale è l'inevitabile prezzo del LOH, il buco può essere riutilizzato solo da un'allocazione per un oggetto di dimensioni uguali o inferiori. Il vero problema è supporre che a un programma dovrebbe essere consentito di consumare tutta la memoria virtuale in qualsiasi momento.

Un problema che altrimenti scompare completamente semplicemente eseguendo il codice su un sistema operativo a 64 bit. Un processo a 64 bit dispone di 8 terabyte di spazio degli indirizzi di memoria virtuale disponibile, 3 ordini di grandezza in più rispetto a un processo a 32 bit. Non puoi rimanere senza buchi.

Per farla breve, LOH rende il codice più efficiente. Al costo di utilizzare lo spazio degli indirizzi di memoria virtuale disponibile meno efficiente.


UPDATE, .NET 4.5.1 ora supporta la compattazione della proprietà LOH, GCSettings.LargeObjectHeapCompactionMode . Attenti alle conseguenze per favore.


3
@ Hans Passant, potresti per favore chiarire sul sistema x64, vuoi dire che questo problema scompare completamente?
Johnny_D

Alcuni dettagli di implementazione del LOH hanno senso, ma alcuni mi lasciano perplesso. Ad esempio, posso capire che se vengono creati e abbandonati molti oggetti di grandi dimensioni, può essere generalmente desiderabile eliminarli in massa in una raccolta Gen2 piuttosto che frammentariamente nelle raccolte Gen0, ma se si crea e si abbandona ad esempio un array di 22.000 stringhe a cui non esistono riferimenti esterni, quale vantaggio esiste nel fatto che le raccolte Gen0 e Gen1 taggano tutte le 22.000 stringhe come "live" senza considerare se esiste alcun riferimento all'array?
supercat

6
Ovviamente il problema della frammentazione è lo stesso su x64. Ci vorranno solo pochi giorni in più per eseguire il processo del server prima che inizi.
Lothar,

1
Hmm, no, non sottovalutare mai 3 ordini di grandezza. Quanto tempo ci vuole per raccogliere i rifiuti in un mucchio di 4 terabyte è qualcosa che non puoi evitare di scoprire molto prima che si avvicini a quello.
Hans Passant

2
@HansPassant Potresti, per favore, elaborare questa affermazione: "Quanto tempo ci vuole per raccogliere i rifiuti in un mucchio di 4 terabyte è qualcosa che non puoi evitare di scoprire molto prima che si avvicini a quello."
relativamente_random

9

Se la dimensione dell'oggetto è maggiore di un valore bloccato (85000 byte in .NET 1), CLR lo inserisce in Large Object Heap. Questo ottimizza:

  1. Allocazione degli oggetti (gli oggetti piccoli non vengono mescolati con oggetti grandi)
  2. Garbage collection (LOH raccolto solo su GC completo)
  3. Deframmentazione della memoria (LOH non viene mai compattato raramente)

9

La differenza essenziale tra Small Object Heap (SOH) e Large Object Heap (LOH) è che la memoria in SOH viene compressa quando raccolta, mentre LOH no, come illustrato in questo articolo . Compattare oggetti di grandi dimensioni costa molto. Simile agli esempi nell'articolo, diciamo che lo spostamento di un byte in memoria richiede 2 cicli, quindi la compattazione di un oggetto da 8 MB in un computer da 2 GHz richiede 8 ms, che è un costo elevato. Considerando che gli oggetti di grandi dimensioni (array nella maggior parte dei casi) sono abbastanza comuni nella pratica, suppongo che questo sia il motivo per cui Microsoft inserisce oggetti di grandi dimensioni nella memoria e propone LOH.

BTW, secondo questo post , LOH di solito non genera problemi di frammenti di memoria.


1
Il caricamento di grandi quantità di dati in oggetti gestiti di solito riduce il costo di 8 ms per compattare LOH. In pratica, nella maggior parte delle applicazioni Big Data, il costo LOH è insignificante rispetto al resto delle prestazioni dell'applicazione.
Shiv

3

Il principio è che è improbabile (e molto probabilmente una cattiva progettazione) che un processo crei molti oggetti di grandi dimensioni di breve durata, quindi CLR alloca oggetti di grandi dimensioni a un heap separato su cui esegue GC con una pianificazione diversa rispetto all'heap normale. http://msdn.microsoft.com/en-us/magazine/cc534993.aspx


Anche l'inserimento di oggetti di grandi dimensioni, diciamo, nella generazione 2 potrebbe finire per danneggiare le prestazioni, dal momento che ci vorrebbe molto tempo per compattare la memoria, specialmente se viene liberata una piccola quantità e oggetti ENORMI devono essere copiati in una nuova posizione. L'attuale LOH non è compattato per motivi di prestazioni.
Christopher Currens

Penso che sia solo un cattivo design perché il GC non lo gestisce bene.
CodesInChaos

@CodeInChaos Apparentemente, ci sono alcuni miglioramenti in arrivo in .NET 4.5
Christian.K

1
@CodeInChaos: sebbene possa avere senso che il sistema attenda fino a una raccolta gen2 prima di provare a recuperare la memoria da oggetti LOH anche di breve durata, non riesco a vedere alcun vantaggio in termini di prestazioni nel dichiarare oggetti LOH (e qualsiasi oggetto a cui tengono riferimenti) dal vivo incondizionatamente durante le raccolte gen0 e gen1. Ci sono delle ottimizzazioni rese possibili da un simile presupposto?
supercat

@supercat Ho guardato il collegamento menzionato da Myles McDonnell. La mia comprensione è: 1. La raccolta LOH avviene in un GC di seconda generazione. 2. La raccolta LOH non include la compattazione (nel momento in cui l'articolo è stato scritto). Invece, contrassegnerà gli oggetti morti come riutilizzabili e questi buchi serviranno alle future allocazioni LOH se abbastanza grandi. A causa del punto 1, considerando che un GC gen 2 sarebbe lento se ci sono molti oggetti in gen 2, penso che sia meglio evitare di usare LOH il più possibile in questo caso.
robbie fan l'

0

Non sono un esperto di CLR, ma immagino che avere un heap dedicato per oggetti di grandi dimensioni possa prevenire inutili sweep GC degli heap generazionali esistenti. L'allocazione di un oggetto di grandi dimensioni richiede una quantità significativa di memoria libera contigua . Per fornire quello dai "buchi" sparsi nei cumuli generazionali, avresti bisogno di frequenti compazioni (che vengono eseguite solo con i cicli GC).

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.