Best practice per forzare la Garbage Collection in C #


118

Nella mia esperienza sembra che la maggior parte delle persone ti dirà che non è saggio forzare una garbage collection ma in alcuni casi in cui lavori con oggetti di grandi dimensioni che non vengono sempre raccolti nella generazione 0 ma dove la memoria è un problema, è va bene forzare il ritiro? C'è una buona pratica là fuori per farlo?

Risposte:


112

La procedura migliore è non forzare una raccolta dei rifiuti.

Secondo MSDN:

"È possibile forzare la raccolta dei rifiuti chiamando Collect, ma la maggior parte delle volte dovrebbe essere evitato perché potrebbe creare problemi di prestazioni."

Tuttavia, se puoi testare in modo affidabile il tuo codice per confermare che la chiamata di Collect () non avrà un impatto negativo, vai avanti ...

Cerca solo di assicurarti che gli oggetti vengano puliti quando non ne hai più bisogno. Se hai oggetti personalizzati, guarda come usare l '"istruzione using" e l'interfaccia IDisposable.

Questo collegamento contiene alcuni buoni consigli pratici per quanto riguarda la liberazione della memoria / raccolta dei rifiuti, ecc:

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx


3
Inoltre, è possibile impostare diversi di LatencyMode msdn.microsoft.com/en-us/library/bb384202.aspx
David d c e Freitas

4
Se i tuoi oggetti puntano alla memoria non gestita, puoi comunicarlo al Garbage Collector tramite GC.AddMemoryPressure Api ( msdn.microsoft.com/en-us/library/… ). Ciò fornisce al garbage collector ulteriori informazioni sul sistema senza interferire con gli algoritmi di raccolta.
Govert

+1: * guarda come usare l '"istruzione using" e l'interfaccia IDisposable. * Non prenderei nemmeno in considerazione la forzatura se non come ultima risorsa - buon consiglio (leggi come' disclaimer '). Tuttavia, sto forzando la raccolta in uno unit test per simulare la perdita di un riferimento attivo in un'operazione di back-end, generando infine un file TargetOfInvocationNullException.
IAbstract

33

Guardala in questo modo: è più efficiente buttare la spazzatura della cucina quando il bidone della spazzatura è al 10% o lasciarlo riempire prima di portarlo fuori?

Non lasciandolo riempire, stai sprecando il tuo tempo a camminare da e per il bidone della spazzatura all'esterno. Questo è analogo a ciò che accade quando viene eseguito il thread GC: tutti i thread gestiti vengono sospesi mentre è in esecuzione. E se non mi sbaglio, il thread GC può essere condiviso tra più AppDomain, quindi la garbage collection interessa tutti loro.

Naturalmente, potresti incontrare una situazione in cui non aggiungerai nulla al bidone della spazzatura in qualsiasi momento presto, ad esempio se hai intenzione di prenderti una vacanza. Quindi, sarebbe una buona idea buttare la spazzatura prima di uscire.

Questa POTREBBE essere una volta che forzare un GC può essere d'aiuto: se il tuo programma è inattivo, la memoria in uso non viene raccolta dai rifiuti perché non ci sono allocazioni.


5
Se avessi un bambino che morirebbe se lo avessi lasciato per più di un minuto e avessi sempre e solo un minuto per gestire la spazzatura, allora vorresti fare un po 'ogni volta invece che tutto in una volta. Sfortunatamente, il metodo GC :: Collect () non è tanto più veloce quanto più spesso lo chiami. Quindi, per un motore in tempo reale, se non puoi semplicemente utilizzare il meccanismo di smaltimento e lasciare che il GC raccolga i tuoi dati, allora non dovresti usare un sistema gestito, come da mia risposta (probabilmente sotto questo, lol).
Jin

1
Nel mio caso, sto eseguendo ripetutamente un algoritmo A * (percorso più breve) per ottimizzarlo ... che, in produzione, verrà eseguito solo una volta (su ogni "mappa"). Quindi voglio che il GC venga eseguito prima di ogni iterazione, al di fuori del mio "blocco di prestazioni misurate", perché ritengo che modelli più da vicino la situazione in produzione, in cui il GC potrebbe / dovrebbe essere forzato dopo aver navigato in ogni "mappa".
corlettk

Chiamare GC prima del blocco misurato in realtà non modella la situazione in produzione, perché in produzione il GC verrà eseguito in tempi imprevedibili. Per mitigare ciò, è necessario eseguire una misurazione lunga che includa diverse esecuzioni GC e tenere conto dei picchi durante GC nelle analisi e nelle statistiche.
Ha corso il

32

La procedura migliore è non forzare una raccolta dei rifiuti nella maggior parte dei casi. (Ogni sistema su cui ho lavorato che aveva forzato la raccolta dei rifiuti, presentava problemi di sottolineatura che, se risolti, avrebbero rimosso la necessità di forzare la raccolta dei rifiuti e accelerato notevolmente il sistema.)

Ci sono alcuni casi in cui si sa di più su l'utilizzo della memoria, allora il garbage collector fa. È improbabile che ciò avvenga in un'applicazione multiutente o in un servizio che risponde a più di una richiesta alla volta.

Tuttavia, in alcune elaborazioni di tipo batch , conosci più del GC. Ad esempio, considera un'applicazione che.

  • Viene fornito un elenco di nomi di file sulla riga di comando
  • Elabora un singolo file, quindi scrive il risultato in un file dei risultati.
  • Durante l'elaborazione del file, crea molti oggetti interconnessi che non possono essere raccolti finché l'elaborazione del file non è stata completata (ad esempio un albero di analisi)
  • Non mantiene molto stato tra i file che ha elaborato .

Si può essere in grado di fare un caso (dopo un'attenta) il test che si dovrebbe forzare una garbage collection completa dopo aver elaborare ogni file.

Un altro caso è un servizio che si sveglia ogni pochi minuti per elaborare alcuni elementi e non mantiene alcuno stato mentre è addormentato . Quindi forzare una raccolta completa appena prima di andare a dormire può essere utile.

L'unica volta che prenderei in considerazione la forzatura di una raccolta è quando so che molti oggetti sono stati creati di recente e pochissimi oggetti sono attualmente referenziati.

Preferirei avere un'API per la raccolta dei rifiuti quando potrei dare suggerimenti su questo tipo di cose senza dover forzare un GC da solo.

Vedi anche " Bocconcini sulle prestazioni di Rico Mariani "


2
Analogia: Playschool (sistema) mantiene i pastelli (risorse). A seconda del numero di bambini (compiti) e della scarsità di colori, l'insegnante (.Net) decide come allocare e condividere tra i bambini. Quando viene richiesto un colore raro, l'insegnante può assegnare dal pool o cercarne uno che non viene utilizzato. L'insegnante ha la facoltà di raccogliere periodicamente i pastelli inutilizzati (raccolta dei rifiuti) per mantenere le cose in ordine (ottimizzare l'uso delle risorse). In genere un genitore (programmatore) non può predeterminare la migliore politica di riordino dei pastelli in classe. È improbabile che il pisolino programmato di un bambino sia un buon momento per interferire con la colorazione degli altri bambini.
AlanK

1
@AlanK, mi piace, perché quando i bambini vanno a casa per la giornata, è un ottimo momento per l'aiutante degli insegnanti per fare un buon ordine senza che i bambini si intromettano. (I sistemi recenti su cui ho lavorato hanno appena riavviato il processo di servizio in tali momenti con l'ordine di forzare GC.)
Ian Ringrose

21

Penso che l'esempio fornito da Rico Mariani sia stato buono: potrebbe essere appropriato attivare un GC se c'è un cambiamento significativo nello stato dell'applicazione. Ad esempio, in un editor di documenti può essere corretto attivare un GC quando un documento viene chiuso.


2
O appena prima di aprire un grande oggetto contiguo che ha mostrato una cronologia di errori e non presenta una risoluzione efficiente per aumentarne la granularità.
crokusek

17

Ci sono poche linee guida generali nella programmazione che sono assolute. La metà delle volte, quando qualcuno dice "stai sbagliando", sta solo sputando una certa quantità di dogma. In C, si temeva per cose come codice o thread che si auto-modificano, nei linguaggi GC forza il GC o, in alternativa, impedisce l'esecuzione del GC.

Come nel caso della maggior parte delle linee guida e delle buone regole pratiche (e delle buone pratiche di progettazione), ci sono rare occasioni in cui ha senso aggirare la norma stabilita. Devi essere molto sicuro di aver compreso il caso, che il tuo caso richiede davvero l'abrogazione della pratica comune e di comprendere i rischi e gli effetti collaterali che puoi causare. Ma ci sono casi del genere.

I problemi di programmazione sono molto vari e richiedono un approccio flessibile. Ho visto casi in cui ha senso bloccare GC in lingue raccolte da rifiuti e luoghi in cui ha senso attivarlo piuttosto che aspettare che si verifichi naturalmente. Il 95% delle volte, uno di questi sarebbe un segno di non aver affrontato correttamente il problema. Ma 1 volta su 20, probabilmente c'è un motivo valido per farlo.


12

Ho imparato a non cercare di superare in astuzia la raccolta dei rifiuti. Detto questo, mi limito a usare la usingparola chiave quando si tratta di risorse non gestite come I / O di file o connessioni al database.


27
compilatore? cosa c'entra il compilatore con GC? :)
KristoferA

1
Niente, compila e ottimizza solo il codice. E questo sicuramente non ha nulla a che fare con CLR ... o anche .NET.
Kon

1
Gli oggetti che occupano molta memoria, come immagini molto grandi, possono finire per non essere raccolti nella spazzatura, a meno che tu non li raccolga esplicitamente. Penso che questo (oggetti di grandi dimensioni) fosse il problema dell'OP, più o meno.
code4life

1
Incartarlo in un utilizzo garantirà che sia pianificato per GC una volta che è fuori dall'ambito di utilizzo. A meno che il computer non esploda, quella memoria molto probabilmente verrà ripulita.
Kon

9

Non sono sicuro che sia una best practice, ma quando lavoro con grandi quantità di immagini in un ciclo (cioè creando e smaltendo molti oggetti grafici / immagine / bitmap), lascio regolarmente GC.Collect.

Penso di aver letto da qualche parte che il GC viene eseguito solo quando il programma è (per lo più) inattivo e non nel mezzo di un ciclo intenso, quindi potrebbe sembrare un'area in cui il GC manuale potrebbe avere senso.


Sei sicuro di averne bisogno? Il GC sarà raccogliere se ha bisogno di memoria, anche se il codice non è inattivo.
Konrad Rudolph

Non sono sicuro di come sia ora in .net 3.5 SP1, ma in precedenza (1.1 e credo di aver testato contro 2.0) ha fatto la differenza nell'utilizzo della memoria. Ovviamente il GC raccoglierà sempre quando necessario, ma potresti comunque finire per sprecare 100 MB di RAM quando ne hai bisogno solo 20. Avrebbero comunque bisogno di alcuni test in più
Michael Stum

2
GC viene attivato sull'allocazione della memoria quando la generazione 0 raggiunge una determinata soglia (ad esempio 1 MB), non quando "qualcosa è inattivo". Altrimenti potresti finire con OutOfMemoryException in un ciclo semplicemente allocando e scartando immediatamente gli oggetti.
liggett78

5
i 100meg di RAM non vengono sprecati se nessun altro processo ne ha bisogno. Ti sta dando un bel miglioramento delle prestazioni :-P
Orion Edwards

9

Un caso che ho riscontrato di recente che richiedeva chiamate manuali è GC.Collect()stato quando si lavora con oggetti C ++ di grandi dimensioni racchiusi in piccoli oggetti C ++ gestiti, a cui si accedeva da C #.

Il Garbage Collector non è mai stato chiamato perché la quantità di memoria gestita utilizzata era trascurabile, ma la quantità di memoria non gestita utilizzata era enorme. Chiamare manualmente Dispose()gli oggetti richiederebbe che tenga traccia di quando gli oggetti non sono più necessari, mentre la chiamata GC.Collect()ripulirà tutti gli oggetti che non sono più indicati ...


6
Un modo migliore per risolvere questo problema è chiamare GC.AddMemoryPressure (ApproximateSizeOfUnmanagedResource)il costruttore e successivamente GC.RemoveMemoryPressure(addedSize)il finalizzatore. In questo modo il garbage collector verrà eseguito automaticamente, tenendo conto delle dimensioni delle strutture non gestite che potrebbero essere raccolte. stackoverflow.com/questions/1149181/...
HugoRune

E un modo ancora migliore per risolvere il problema è chiamare effettivamente Dispose (), cosa che dovresti fare comunque.
fabspro

2
Il modo migliore è usare la struttura Using. Prova / Finalmente Smaltire è una seccatura
TamusJRoyce

7

Penso che tu abbia già elencato la migliore pratica e che NON sia usarla a meno che NON sia DAVVERO necessario. Consiglio vivamente di esaminare il codice in modo più dettagliato, utilizzando potenzialmente strumenti di profilazione, se necessario, per rispondere prima a queste domande.

  1. C'è qualcosa nel codice che dichiara gli elementi in un ambito più ampio del necessario
  2. L'utilizzo della memoria è davvero troppo alto
  3. Confronta le prestazioni prima e dopo l'utilizzo di GC.Collect () per vedere se aiuta davvero.

5

Supponiamo che il tuo programma non abbia perdite di memoria, gli oggetti si accumulino e non possano essere modificati con GC in Gen 0 perché: 1) Sono referenziati per molto tempo quindi entra in Gen1 & Gen2; 2) Sono oggetti di grandi dimensioni (> 80K) quindi entra in LOH (Large Object Heap). E LOH non esegue la compattazione come in Gen0, Gen1 e Gen2.

Controlla il contatore delle prestazioni di ".NET Memory" puoi vedere che il 1) problema non è davvero un problema. In genere, ogni 10 Gen0 GC attiverà 1 Gen1 GC e ogni 10 Gen1 GC attiverà 1 Gen2 GC. Teoricamente, GC1 e GC2 non possono mai essere modificati con GC se non c'è pressione su GC0 (se l'utilizzo della memoria del programma è realmente cablato). A me non succede mai.

Per il problema 2), puoi controllare il contatore delle prestazioni della "Memoria .NET" per verificare se LOH si sta gonfiando. Se è davvero un problema per il tuo problema, forse puoi creare un pool di oggetti di grandi dimensioni come suggerisce questo blog http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx .


4

Gli oggetti di grandi dimensioni vengono allocati su LOH (large object heap), non su gen 0. Se stai dicendo che non vengono raccolti in modo indesiderato con gen 0, hai ragione. Credo che vengano raccolti solo quando si verifica l'intero ciclo GC (generazioni 0, 1 e 2).

Detto questo, credo che dall'altra parte GC si adatterà e raccoglierà la memoria in modo più aggressivo quando si lavora con oggetti di grandi dimensioni e la pressione della memoria aumenta.

È difficile dire se collezionare o meno e in quali circostanze. Facevo GC.Collect () dopo aver eliminato finestre / moduli di dialogo con numerosi controlli ecc. (Perché nel momento in cui il modulo ei suoi controlli finiscono nella seconda generazione a causa della creazione di molte istanze di oggetti di business / caricamento di molti dati - no oggetti di grandi dimensioni ovviamente), ma in realtà non ha notato alcun effetto positivo o negativo a lungo termine in questo modo.


4

Vorrei aggiungere che: Calling GC.Collect () (+ WaitForPendingFinalizers ()) è una parte della storia. Come giustamente menzionato da altri, GC.COllect () è una raccolta non deterministica ed è lasciata alla discrezione del GC stesso (CLR). Anche se aggiungi una chiamata a WaitForPendingFinalizers, potrebbe non essere deterministica. Prendi il codice da questo collegamento msdn ed esegui il codice con l'iterazione del loop dell'oggetto come 1 o 2. Troverai cosa significa non deterministico (imposta un punto di interruzione nel distruttore dell'oggetto). Precisamente, il distruttore non viene chiamato quando c'erano solo 1 (o 2) oggetti persistenti da Wait .. (). [Citation reqd.]

Se il codice ha a che fare con risorse non gestite (es: handle di file esterni), è necessario implementare i distruttori (o finalizzatori).

Ecco un esempio interessante:

Nota : se hai già provato l'esempio precedente da MSDN, il codice seguente cancellerà l'aria.

class Program
{    
    static void Main(string[] args)
        {
            SomePublisher publisher = new SomePublisher();

            for (int i = 0; i < 10; i++)
            {
                SomeSubscriber subscriber = new SomeSubscriber(publisher);
                subscriber = null;
            }

            GC.Collect();
            GC.WaitForPendingFinalizers();

            Console.WriteLine(SomeSubscriber.Count.ToString());


            Console.ReadLine();
        }
    }

    public class SomePublisher
    {
        public event EventHandler SomeEvent;
    }

    public class SomeSubscriber
    {
        public static int Count;

        public SomeSubscriber(SomePublisher publisher)
        {
            publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
        }

        ~SomeSubscriber()
        {
            SomeSubscriber.Count++;
        }

        private void publisher_SomeEvent(object sender, EventArgs e)
        {
            // TODO: something
            string stub = "";
        }
    }

Suggerisco, prima di analizzare quale potrebbe essere l'output, quindi di eseguire e quindi leggere il motivo di seguito:

{Il distruttore viene chiamato implicitamente solo quando il programma termina. } Per pulire deterministicamente l'oggetto, è necessario implementare IDisposable ed effettuare una chiamata esplicita a Dispose (). Questa è l'essenza! :)


2

Un'altra cosa, l'attivazione esplicita di GC Collect NON può migliorare le prestazioni del programma. È del tutto possibile peggiorare le cose.

.NET GC è ben progettato e ottimizzato per essere adattivo, il che significa che può regolare la soglia GC0 / 1/2 secondo l '"abitudine" dell'utilizzo della memoria del programma. Quindi, sarà adattato al tuo programma dopo un po 'di tempo. Dopo aver richiamato esplicitamente GC.Collect, le soglie verranno ripristinate! E .NET deve impiegare del tempo per adattarsi di nuovo all '"abitudine" del programma.

Il mio suggerimento è sempre fidarsi di .NET GC. Qualsiasi problema di memoria emerge, controlla il contatore delle prestazioni ".NET Memory" e diagnostica il mio codice.


6
Penso che sia meglio unire questa risposta con la tua risposta precedente.
Salamander

1

Non sono sicuro che sia una buona pratica ...

Suggerimento: non implementare questo o altro in caso di dubbi. Rivaluta quando i fatti sono noti, quindi esegui test delle prestazioni prima / dopo per verificare.


0

Tuttavia, se puoi testare in modo affidabile il tuo codice per confermare che la chiamata di Collect () non avrà un impatto negativo, vai avanti ...

IMHO, questo è simile a dire "Se puoi provare che il tuo programma non avrà mai bug in futuro, allora vai avanti ..."

In tutta serietà, forzare il GC è utile per scopi di debug / test. Se ritieni di doverlo fare altre volte, allora o ti sbagli o il tuo programma è stato costruito male. In ogni caso, la soluzione non è costringere il GC ...


"allora o ti sbagli, o il tuo programma è stato costruito male. In ogni caso, la soluzione non è forzare il GC ..." Gli assoluti sono quasi sempre non veri. Ci sono alcune circostanze eccezionali che ha senso.
rotola il
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.