Migliorare le prestazioni di Garbage Collection
Per le app UWP (Universal Windows Platform) scritte in C# e Visual Basic la gestione della memoria avviene automaticamente tramite .NET Garbage Collector. Questa sezione contiene un riepilogo delle procedure consigliate correlate a .NET Garbage Collector nelle app UWP. Per altre info sul funzionamento di .NET Garbage Collector e sugli strumenti per il debug e l'analisi delle prestazioni di Garbage Collector, vedi Garbage Collection.
Nota La necessità di intervenire sul comportamento predefinito di Garbage Collector è una chiara indicazione della presenza di problemi di memoria generali nella tua app. Per altre info, vedi la pagina relativa allo strumento Utilizzo memoria durante il debug in Visual Studio 2015. Questo argomento riguarda solo C# e Visual Basic.
Garbage Collector stabilisce quando avviare l'esecuzione bilanciando l'utilizzo di memoria dell'heap gestito con la quantità di lavoro che un'operazione di Garbage Collection deve eseguire. A questo scopo, Garbage Collector può dividere l'heap in generazioni e raccogliere, nella maggior parte dei casi, solo una parte dell'heap. Nell'heap gestito sono presenti tre generazioni:
- Generazione 0. Questa generazione contiene gli oggetti appena allocati, a meno che non siano pari o superiori a 85 KB, nel qual caso faranno parte dell'heap di oggetti di grandi dimensioni, che viene creato con le raccolte della generazione 2. Le raccolte della generazione 0 sono il tipo di raccolta più frequente ed eseguono la pulitura degli oggetti di breve durata, ad esempio le variabili locali.
- Prima generazione. Questa generazione contiene oggetti che sono sopravvissuti alle raccolte della generazione 0 e funge da buffer tra la generazione 0 e la generazione 2. Le raccolte della generazione 1 vengono eseguite con una frequenza minore rispetto a quelle della generazione 0 ed eseguono la pulizia degli oggetti temporanei che erano attivi durante le precedenti raccolte della generazione 0. Una raccolta della generazione 1 raccoglie anche la generazione 0.
- Generazione 2. Questa generazione contiene oggetti di lunga durata che sono sopravvissuti alle raccolte della generazione 0 e della generazione 1. Le raccolte della generazione 2 sono le meno frequenti e raccolgono l'intero heap gestito, incluso l'heap di oggetti di grandi dimensioni che contiene oggetti pari o superiori a 85 KB.
Puoi misurare le prestazioni del Garbage Collector da due punti di vista: il tempo necessario per l'operazione di Garbage Collection e il consumo di memoria dell'heap gestito. Se disponi di un'app ridotta con una dimensione di heap inferiore a 100 MB, concentrati sulla riduzione del consumo di memoria. Se un'app presenta un heap gestito superiore a 100 MB, allora concentrati solo sulla riduzione del tempo di Garbage Collection. Ecco come puoi migliorare le prestazioni del Garbage Collector .NET.
Ridurre il consumo di memoria
Rilasciare i riferimenti
Un riferimento a un oggetto nell'app impedisce che l'oggetto in questione, e tutti gli oggetti a cui esso fa riferimento, vengano raccolti. Un'attività utile è quella eseguita dal compilatore .NET che è in grado di rilevare quando una variabile non è più in uso e gli oggetti in essa contenuti sono quindi idonei per la raccolta. Purtroppo, in alcuni casi, potrebbe non essere ovvio che alcuni oggetti presentano un riferimento ad altri oggetti, in quanto parte del grafico dell'oggetto potrebbe essere di proprietà di librerie usate dall'app. Per informazioni sugli strumenti e sulle tecniche per individuare gli oggetti che non vengono sottoposti a un'operazione di Garbage Collection, vedi l'argomento relativo alla Garbage Collection e alle prestazioni.
Indurre una Garbage Collection se utile
Induci una Garbage Collection solo se hai misurato le prestazioni della tua app e ritieni che l'operazione potrà migliorarle.
Puoi indurre un processo di Garbage Collection di una generazione chiamando GC.Collect(n), dove n è la generazione da raccogliere (0, 1 o 2).
Note Ti consigliamo di non forzare un processo di Garbage Collection nell'app, perché Garbage Collector usa diversi criteri euristici per stabilire il momento migliore per l'esecuzione di una raccolta e una raccolta forzata comporta in molti casi un uso superfluo della CPU. Se invece la tua app contiene numerosi oggetti che non sono più in uso e vuoi restituire questa memoria al sistema, potrebbe essere opportuno forzare una Garbage Collection. Puoi ad esempio indurre una raccolta alla fine di una sequenza di caricamento in un gioco per liberare memoria prima dell'inizio del gioco. Per evitare di indurre inavvertitamente un numero eccessivo di Garbage Collection, puoi impostare GCCollectionMode su Optimized. In questo modo indichi al Garbage Collector di avviare una raccolta solo dopo aver determinato che il risultato dell'operazione sarà abbastanza produttivo da giustificarla.
Ridurre il tempo di Garbage Collection
Le informazioni in questa sezione sono utili solo dopo avere analizzato la tua app e avere osservato tempi di Garbage Collection eccessivi. I tempi di pausa correlati alla Garbage Collection includono il tempo necessario per l'esecuzione di un singolo passaggio di Garbage Collection e il tempo totale impiegato dall'app per le varie operazioni di Garbage Collection. La quantità di tempo necessaria per eseguire una raccolta dipende dal numero di dati attivi che il Garbage Collector deve analizzare. La generazione 0 e la generazione 1 sono associate per le dimensioni ma la generazione 2 continua a crescere con il progressivo aumento del numero di oggetti a lunga durata attivi nell'app. Questo significa che i tempi di raccolta per la generazione 0 e la generazione 1 sono associati, ma le raccolte della generazione 2 possono richiedere una quantità di tempo maggiore. La frequenza di esecuzione delle Garbage Collection dipende principalmente dalla quantità di memoria allocata, in quanto una Garbage Collection libera memoria per soddisfare le richieste di allocazione.
Il Garbage Collector a volte sospende l'app per eseguire le proprie attività, ma non necessariamente per tutto il tempo di esecuzione di una raccolta. I tempi di pausa in genere non sono percepibili dall'utente dell'app, soprattutto per le raccolte della generazione 0 e della generazione 1. La funzionalità di Garbage Collection in background del .NET Garbage Collector consente l'esecuzione simultanea delle raccolte della generazione 2 mentre l'app è in esecuzione e sospende l'app solo per brevi periodi di tempo. Tuttavia, non è sempre possibile eseguire una raccolta in background della generazione 2. In questo caso, è possibile che l'utente percepisca la pausa se l'heap disponibile è piuttosto grande (superiore ai 100 MB).
Operazioni di Garbage Collection frequenti possono contribuire a un consumo maggiore della CPU e quindi di energia, a tempi di caricamento più lunghi e a frequenze dei fotogrammi ridotte nell'applicazione. Di seguito sono riportate alcune tecniche che puoi usare per ridurre i tempi di Garbage Collection e le pause correlate alle raccolte nell'app UWP gestita.
Ridurre le allocazioni di memoria
Se non allochi oggetti, il Garbage Collector non viene eseguito a meno che nel sistema non esista una condizione di memoria insufficiente. La riduzione della quantità di memoria allocata si traduce direttamente in Garbage collection meno frequenti.
Se non sono auspicabili pause in determinate sezioni dell'app, puoi preallocare gli oggetti necessari in anticipo durante un intervallo di tempo meno critico per le prestazioni. Un gioco può ad esempio allocare tutti gli oggetti necessari durante la schermata di caricamento di un livello ma non eseguire allocazioni mentre è in esecuzione. In questo modo si evitano pause mentre l'utente gioca ed è possibile ottenere una frequenza dei fotogrammi più elevata e coerente.
Riduci le raccolte della generazione 2 evitando gli oggetti di media durata
Le Garbage Collection generazionali offrono prestazioni migliori quando nell'app sono presenti oggetti con una durata realmente breve e/o realmente lunga. Gli oggetti di breve durata vengono riuniti nelle raccolte della generazione 0 e della generazione 1 che sono meno dispendiose, mentre gli oggetti di lunga durata vengono promossi alla generazione 2, che viene raccolta raramente. Gli oggetti di lunga durata sono quelli in uso per tutta la durata dell'app o per un periodo di tempo significativo dell'app, ad esempio mentre è attiva una pagina o un livello di gioco specifico.
Se crei spesso oggetti con una durata temporanea ma sufficiente per essere promossi alla generazione 2, le dispendiose raccolte della generazione 2 aumentano. Puoi provare a ridurre le raccolte della generazione 2 riciclando gli oggetti esistenti o rilasciando gli oggetti in modo più rapido.
Un esempio comune di oggetti con una durata media sono gli oggetti usati per visualizzare le voci di un elenco che un utente scorre. Se tali oggetti vengono creati quando si esegue lo scorrimento delle voci dell'elenco nella visualizzazione, ma non vengono più referenziati quando lo scorrimento delle voci dell'elenco avviene all'esterno della visualizzazione, l'app presenta in genere un numero elevato di raccolte della generazione 2. In queste situazioni puoi preallocare e riutilizzare una serie di oggetti per i dati visualizzati all'utente e utilizzare oggetti di breve durata per caricare le informazioni via via che le voci dell'elenco vengono visualizzate.
Riduci le raccolte della generazione 2 evitando gli oggetti di grandi dimensioni ma di breve durata
Tutti gli oggetti pari o superiori a 85 KB vengono allocati nell'heap di oggetti di grandi dimensioni e raccolti durante la generazione 2. In presenza di variabili temporanee, ad esempio i buffer, che sono superiori a 85 KB, una raccolta della generazione 2 ne esegue la pulitura. Limitando le variabili temporanee a dimensioni inferiori a 85 KB, si riduce il numero di raccolte della generazione 2 all'interno dell'app. Una tecnica comune consiste nella creare un pool di buffer e riutilizzare gli oggetti del pool per evitare allocazioni temporanee di grandi dimensioni.
Evitare oggetti ricchi di riferimenti
Il Garbage Collector stabilisce quali oggetti sono attivi seguendo i riferimenti tra gli oggetti, a partire dalle radici nell'app. Per altre info, vedi l'argomento relativo alle operazioni eseguite durante una garbage collection. Se un oggetto contiene molti riferimenti, il lavoro per il Garbage Collector sarà maggiore. Una tecnica comune, soprattutto con gli oggetti di grandi dimensioni, consiste nel convertire gli oggetti ricchi di riferimenti in oggetti senza riferimenti, ad esempio anziché archiviare un riferimento, si archivia un indice. Naturalmente questa tecnica funziona solo quando è logico applicarla.
La sostituzione dei riferimenti agli oggetti con indici può essere una modifica complicata e dannosa per l'app ed è più efficace per gli oggetti grandi con un numero elevato di riferimenti. Ti consigliamo di eseguirla solo se osservi tempi di Garbage Collection eccessivi nella tua app rispetto agli oggetti ricchi di riferimenti.