2009-07-09 11 views
16

Ho un problema in cui un paio di array tridimensionali allocano un'enorme quantità di memoria e il programma a volte ha bisogno di sostituirli con quelli più grandi/più piccoli e lancia un'eccezione OutOfMemoryException.Force Garbage Collection di array, C#

Esempio: ci sono 5 array assegnati da 96MB (200x200x200, 12 byte di dati in ogni voce) e il programma deve sostituirli con 210x210x210 (111MB). Lo fa in un modo simile a questo:

array1 = new Vector3[210,210,210]; 

Dove array1-array5 sono gli stessi campi utilizzati in precedenza. Questo dovrebbe impostare i vecchi array come candidati per la garbage collection ma apparentemente il GC non agisce abbastanza velocemente e lascia i vecchi array allocati prima di allocare quelli nuovi - il che causa l'OOM - mentre se essi dovessero liberare prima delle nuove allocazioni lo spazio dovrebbe essere abbastanza.

Quello che sto cercando è un modo per fare qualcosa di simile:

GC.Collect(array1) // this would set the reference to null and free the memory 
array1 = new Vector3[210,210,210]; 

Non sono sicuro se uno spazzatura collecion completa sarebbe una buona idea, dato che il codice può (in alcune situazioni) devono essere eseguiti abbastanza spesso.

Esiste un modo corretto per farlo?

+2

Sembra davvero strano che ciò possa causare un'eccezione OOM. Avete qualche codice di esempio che dimostra il problema? – aquinas

+0

Questo fa parte di un programma ragionevolmente grande (ancora inedito) e sfortunatamente posso pubblicare solo piccoli frammenti del codice attuale, niente di abbastanza grande da essere compilabile. –

+0

Il problema non è la gestione della memoria, ma l'utilizzo di matrici troppo grandi. Perché pensi che hai bisogno di quei grandi array in primo luogo? Per cosa li stai usando? – Guffa

risposta

14

Questa non è una risposta esatta alla domanda iniziale, "come forzare GC', eppure, penso che vi aiuterà a riesaminare il problema.

Dopo aver visto il tuo commento,

  • Mettere il GC.Raccogliere(); sembra aiutare, anche se non risolve il problema completamente - per qualche motivo il programma si blocca ancora quando sono allocati circa 1,3 GB (sto usando System.GC.GetTotalMemory (false) per trovare l'importo reale allocato).

io sospetta che si può avere frammentazione della memoria. Se l'oggetto è di grandi dimensioni (85000 byte in .net 2.0 CLR se ricordo correttamente, non so se è stato modificato o meno), l'oggetto verrà allocato in un heap speciale, Large Object Heap (LOH). GC non recupera la memoria utilizzata da oggetti non raggiungibili in LOH, tuttavia, non esegue la compattazione, in LOH come per gli altri heap (gen0, gen1 e gen2), a causa delle prestazioni.

Se si assegna e deallocate spesso oggetti di grandi dimensioni, renderà frammentato LOH e anche se si dispone di più memoria libera in totale rispetto a ciò che è necessario, potrebbe non essere più uno spazio di memoria contiguo, quindi, otterrà l'eccezione OutOfMemory .

Posso pensare a due soluzioni alternative in questo momento.

  1. Move to 64-bit macchina/OS e approfittarne :) (più semplice, ma forse più dura così a seconda delle vostre vincoli di risorse)
  2. Se non è possibile fare # 1, quindi provare a destinare una prima di tutto, è possibile utilizzare un enorme pezzo di memoria (potrebbe essere necessario scrivere qualche classe helper per manipolare un array più piccolo, che di fatto risiede in un array più grande) per evitare la frammentazione. Questo potrebbe aiutare un po ', tuttavia, potrebbe non risolvere completamente il problema e potresti dover affrontare la complessità.
+2

Oppure passare a utilizzare un array frastagliato - Vector3 [210] [210] [210], che dividerebbe le allocazioni e non utilizzare un grande grande blocco di memoria – thecoop

+0

Sembra che il problema sia effettivamente causato da questo problema di frammentazione. Sfortunatamente non riesco a portare il programma a 64 bit perché XNA supporta solo 32 bit. L'allocazione di un grande array all'avvio probabilmente non varrebbe la pena di memoria aggiuntiva nei casi di piccole dimensioni (inferiori a 100x100x100) e il lavoro extra per farlo funzionare correttamente. Ho fatto i calcoli e anche se potessi usare l'intero 2GB il miglioramento delle dimensioni massime del volume non sarebbe abbastanza grande (da circa 200^3 a 300^3) per passare attraverso il problema di trovare/implementare un trucco per fare funziona, almeno non per ora. Grazie a tutti coloro che hanno cercato di aiutare! –

7

Forzare una Garbage Collection non è sempre una buona idea (può effettivamente promuovere la durata degli oggetti in alcune situazioni). Se si deve, si può usare:

array1 = null; 
GC.Collect(); 
array1 = new Vector3[210,210,210]; 
+2

No, non dovresti chiamare GC.Collect. La soluzione migliore è semplicemente rimuovere il riferimento prima che il nuovo array sia allocato e lasciare che il garbage collector faccia funzionare senza interferire. – Guffa

+5

Se il runtime non riesce a trovare memoria sufficiente per l'allocazione, attiverà una raccolta, quindi non è necessario effettuare una chiamata esplicita a GC.Collect. –

+3

John Doe ha chiesto di forzare la raccolta dei dati inutili * dopo tutto. – icelava

1

Essi potrebbero non essere sempre raccolti perché sono in corso di riferimento da qualche parte che non ti aspetti.

Come prova, provare a modificare i riferimenti su WeakReferences e vedere se questo risolve il problema di OOM. Se così non fosse, ti stai riferendo da qualche altra parte.

+0

Vengono utilizzati solo nel codice che li genera e nel codice che utilizza i dati (XNA VertexBuffer, utilizzato per il rendering di un volume) quindi non penso che nient'altro li faccia riferimento. Non ho mai usato WeakReferences prima, ma da quell'articolo ho una possibilità che vengano rilasciati durante il resto dell'esecuzione - e ciò creerebbe un problema serio per il resto del codice. –

2

Se dovessi speculare si problema non è davvero che si sta andando da Vector3 [200.200.200] a un Vector3 [210.210.210] ma che molto probabilmente avete simili passaggi precedenti prima di questo:

 
i.e. 
    // first you have 
    Vector3[10,10,10]; 
    // then 
    Vector3[20,20,20]; 
    // then maybe 
    Vector3[30,30,30]; 
    // .. and so on .. 
    // ... 
    // then 
    Vector3[200,200,200]; 
    // and eventually you try 
    Vector3[210,210,210] // and you get an OutOfMemoryException.. 

Se è vero, suggerirei una migliore strategia di allocazione. Cerca di allocare più volte, magari raddoppiando le dimensioni ogni volta, invece di assegnare sempre solo lo spazio di cui hai bisogno. Soprattutto se questi array sono sempre più utilizzati dagli oggetti che hanno bisogno di appuntare i buffer (cioè se che hanno legami con codice nativo)

Così, invece di quanto sopra, avere qualcosa di simile:

// first start with an arbitrary size 
Vector3[64,64,64]; 
// then double that 
Vector3[128,128,128]; 
// and then.. so in thee steps you go to where otherwise 
// it would have taken you 20.. 
Vector3[256,256,256]; 
+0

Nota che alcune delle classi di raccolta incorporate faranno questo per te. – Brian

+0

Sono d'accordo, riallocando costantemente gli array come se stessero solo chiedendo errori OOM. –

+0

In realtà il "ridimensionamento" degli array viene attivato dall'input dell'utente e non è prevedibile (anche se lo sto aumentando manualmente nei passaggi da testare). L'uso di overallocating non è un'opzione, che in effetti lo farebbe crashare prima, a causa del fatto che le dimensioni totali aumentano in maniera cubica. –

0

Un OutOfMemory l'eccezione attiva automaticamente un ciclo GC una volta e tenta nuovamente l'allocazione prima di lanciare effettivamente l'eccezione al codice. L'unico modo in cui potresti avere eccezioni OutOfMemory è se hai dei riferimenti a troppa memoria. Cancellare i riferimenti non appena possibile assegnandoli nulli.

4

Non si tratta solo della frammentazione di un heap di oggetti di grandi dimensioni? Oggetti> 85.000 byte allocati sull'heap di oggetti di grandi dimensioni. Il GC libera spazio in questo heap ma non compatta mai gli oggetti rimanenti. Ciò può causare un'insufficiente memoria contigua per allocare correttamente un oggetto di grandi dimensioni.

Alan.

0

Parte del problema potrebbe essere l'allocazione di un array multidimensionale, rappresentato come un singolo blocco contiguo di memoria sull'heap di oggetti di grandi dimensioni (ulteriori dettagli here). Questo può bloccare altre allocazioni in quanto non esiste un blocco contiguo libero da usare, anche se c'è ancora dello spazio libero da qualche parte, da cui l'OOM.

Prova assegnandolo come matrice irregolare - Vector3 [210] [210] [210] - che si diffonde le matrici intorno memoria piuttosto che come un unico blocco, e vedere se questo migliora le cose

1

Capisco quello che il tentativo di fare e spingere per la raccolta dei rifiuti immediata non è probabilmente l'approccio giusto (dal momento che il GC è sottile nei suoi modi e veloce alla rabbia).

Detto questo, se si desidera la funzionalità, perché non crearla?

public static void Collect(ref object o) 
{ 
    o = null; 
    GC.Collect(); 
} 
+0

Questo non fa niente. Stai facendo una copia del riferimento all'oggetto o punta a null, che non fa nulla all'oggetto originale o. –

+0

Capisci che se applico una variabile ref, cambia anche la copia del chiamante, giusto? – plinth

+0

Avere un +1 su di me, questo codice imposterà infatti la variabile originale in modo che punti a null poiché sta modificando il puntatore della variabile originale come riferimento. –

9

Sembra che si sia verificato un problema di frammentazione LOH (heap di oggetti di grandi dimensioni).

Large Object Heap

CLR Inside Out Large Object Heap Uncovered

È possibile controllare per vedere se hai problemi di frammentazione Loh utilizzando SOS

Scegli questa question per un esempio di come utilizzare SOS per ispezionare il Loh.

0

John, Creazione di oggetti> 85000 byte faranno finire l'oggetto nell'heap di oggetto di grandi dimensioni. L'heap di oggetti di grandi dimensioni non viene mai compattato, ma lo spazio libero viene riutilizzato di nuovo. Ciò significa che se si assegnano array più grandi ogni volta, è possibile finire in situazioni in cui LOH è frammentato, da cui l'OOM.

è possibile verificare questo è il caso rompendo con il debugger al punto di OOM e ottenendo un dump, l'invio di questo dump alla MS attraverso un bug di connessione (http://connect.microsoft.com) sarebbe un ottimo inizio.

Quello che posso assicurarvi è che il GC farà la cosa giusta cercando di soddisfare la richiesta di allocazione, questo include il lancio di un GC per pulire la vecchia spazzatura per soddisfare le nuove richieste di allocazione.

Non so quale sia la politica di condivisione dei dump della memoria su Stackoverflow, ma sarei felice di dare un'occhiata per capire meglio il tuo problema.