8

La mia applicazione esegue una buona parte della serializzazione binaria e della compressione di oggetti di grandi dimensioni. Non compresso il set di dati serializzato è di circa 14 MB. Compresso è di circa 1,5 MB. Trovo che ogni volta che chiamo il metodo serialize sul mio set di dati il ​​mio contatore di prestazioni di heap di oggetti di grandi dimensioni salta da meno di 1 MB a circa 90 MB. So anche che in un sistema caricato relativamente pesante, di solito dopo un po 'di esecuzione (giorni) in cui questo processo di serializzazione avviene un po' di tempo, è noto che l'applicazione elimina le eccitazioni di memoria quando viene chiamato questo metodo di serializzazione anche se sembra un sacco di memoria. Immagino che la questione sia la frammentazione (anche se non posso dire di essere sicura al 100%, sono abbastanza vicina)Devo chiamare GC.Collect immediatamente dopo aver utilizzato l'heap di oggetti di grandi dimensioni per impedire la frammentazione

La soluzione più semplice a breve termine (credo che cerco sia a breve termine e una risposta a lungo termine) posso pensare di chiamare GC.Collect subito dopo aver terminato il processo di serializzazione. Questo, a mio parere, spazzerà l'oggetto dal LOH e lo farà probabilmente PRIMA che altri oggetti possano essere aggiunti ad esso. Ciò consentirà ad altri oggetti di adattarsi strettamente agli oggetti rimanenti nell'heap senza causare molta frammentazione.

Oltre a questa ridicola allocazione di 90 MB, non credo di avere nient'altro che usi una perdita del LOH. Questa allocazione di 90 MB è anche relativamente rara (intorno ogni 4 ore). Ovviamente avremo ancora l'array da 1,5 MB e forse alcuni altri oggetti serializzati più piccoli.

Qualche idea?

aggiornamento a seguito di buone risposte

Ecco il mio codice che fa il lavoro. In realtà ho provato a cambiarlo per comprimere la serializzazione WHILE in modo che la serializzazione sia serializzata su un flusso allo stesso tempo e non ottengo risultati migliori. Ho anche provato a preallocare il flusso di memoria a 100 MB e provare a utilizzare lo stesso flusso due volte di fila, il LOH sale comunque a 180 MB. Sto usando Process Explorer per monitorarlo. È da pazzi. Penso che proverò l'idea UnmanagedMemoryStream successiva.

Vorrei incoraggiarvi ragazzi a provarlo se non lo volete. Non deve essere questo codice esatto. Basta serializzare un grande insieme di dati e si ottengono risultati sorprendenti (il mio ha un sacco di tavoli, arround 15 e un sacco di archi e colonne)

 byte[] bytes; 
     System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serializer = 
     new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();    
     System.IO.MemoryStream memStream = new System.IO.MemoryStream(); 
     serializer.Serialize(memStream, obj); 
     bytes = CompressionHelper.CompressBytes(memStream.ToArray()); 
     memStream.Dispose(); 
     return bytes; 

aggiornamento dopo aver provato la serializzazione binaria con UnmanagedMemoryStream

Anche se io serializzato su un UnmanagedMemoryStream il LOH salta alla stessa dimensione. Sembra che non importa quello che faccio, chiamato BinaryFormatter per serializzare questo oggetto di grandi dimensioni utilizzerà il LOH. Per quanto riguarda la pre-allocazione, non sembra essere di grande aiuto. Suppongo di pre-allocare dire che ho preallocato 100 MB, quindi ho serializzato, userà 170 MB. Ecco il codice per questo. Ancora più semplice rispetto al codice sopra

BinaryFormatter serializer = new BinaryFormatter(); 
MemoryStream memoryStream = new MemoryStream(1024*1024*100); 
GC.Collect(); 
serializer.Serialize(memoryStream, assetDS); 

Il GC.Collect() in mezzo c'è solo per aggiornare il contatore delle prestazioni LOH. Vedrai che assegnerà i 100 MB corretti. Ma quando chiamate il serialize, noterete che sembra aggiungere quello sopra il 100 che avete già assegnato.

+4

C'è qualcosa che puoi fare per serializzare direttamente su un 'Stream' invece di usare il LOH come buffer? –

risposta

3

Sfortunatamente, l'unico modo per risolvere il problema era quello di suddividere i dati in blocchi in modo da non allocare grandi blocchi sul LOH. Tutte le risposte proposte qui erano buone e ci si aspettava che lavorassero, ma non lo fecero.Sembra che la serializzazione binaria in .NET (usando .NET 2.0 SP2) faccia la sua piccola magia sotto il cofano che impedisce agli utenti di avere il controllo sull'assegnazione della memoria.

Risposta quindi alla domanda "non è probabile che funzioni". Quando si tratta di utilizzare la serializzazione .NET, la soluzione migliore è serializzare gli oggetti di grandi dimensioni in blocchi più piccoli. Per tutti gli altri scenari, le risposte di cui sopra sono grandiose.

2

90 MB di RAM non è molto.

Evita di chiamare GC.Collect a meno che tu non abbia un problema. Se hai un problema e non c'è una soluzione migliore, prova a chiamare GC.Collect e verifica se il tuo problema è risolto.

4

Attenzione al modo in cui le classi e i flussi di raccolta come MemoryStream funzionano in .NET. Hanno un buffer sottostante, una matrice semplice. Ogni volta che il buffer di raccolta o flusso cresce oltre la dimensione allocata dell'array, la matrice viene riallocata, ora al doppio della dimensione precedente.

Ciò può causare molte copie dell'array nel LOH. Il tuo set di dati da 14 MB inizierà a utilizzare il LOH a 128 KB, quindi prenderà un altro 256 KB, quindi un altro 512 KB, eccetera. L'ultimo, quello effettivamente utilizzato, sarà di circa 16 MB. Il LOH contiene la somma di questi, circa 30 MB, solo uno dei quali è in uso.

Fai questo tre volte senza una raccolta gen2 e il tuo LOH è cresciuto a 90 MB.

Evitare questo pre-allocando il buffer alle dimensioni previste. MemoryStream ha un costruttore che prende una capacità iniziale. Quindi fai tutte le classi di raccolta. Chiamare GC.Collect() dopo aver annullato tutti i riferimenti può aiutare a sbloccare il LOH ed eliminare quei buffer intermedi, al costo di intasare gli heap gen1 e gen2 troppo presto.

0

Se è davvero necessario utilizzare il LOH per qualcosa come un servizio o qualcosa che deve essere in esecuzione per un lungo periodo, è necessario utilizzare pool di buffer che non sono mai stati deallocati e che è possibile allocare idealmente all'avvio. Questo significa che dovrai fare la tua 'gestione della memoria' per questo, ovviamente.

A seconda di ciò che si sta facendo con questa memoria, potrebbe anche essere necessario p/Invocare sul codice nativo per le parti selezionate per evitare di dover chiamare alcune API .NET che obbligano a mettere i dati nello spazio appena allocato nel LOH.

Questo è un buon punto di partenza articolo sui problemi: http://blogs.msdn.com/maoni/archive/2004/12/19/327149.aspx

mi piacerebbe prendere in considerazione voi molto fortunati se si trucco GC avrebbe funzionato, e sarebbe davvero funzionano solo se non c'è molto in corso al stessa ora nel sistema. Se lavori in parallelo, questo ritarderà leggermente l'inevitabile.

Leggete anche la documentazione relativa a GC.Collect.IIRC, GC.Collect (n) dice solo che non raccoglie oltre la generazione n - non che in realtà DEVE mai arrivare alla generazione n.

0

Non preoccuparti delle dimensioni LOH che salta. Preoccuparsi di allocare/deallocare LOH. .Net molto stupido riguardo a LOH - piuttosto che allocare oggetti LOH lontano dall'heap normale, si alloca alla prossima pagina VM disponibile. Ho un'app 3D che fa molto allocare/deallocare sia di LOH che di oggetti regolari - il risultato (come si vede nel report di debug di DebugDiag) è che le pagine di piccolo heap e di heap di grandi dimensioni si alternano in tutta la RAM, fino a quando non ci sono grandi blocchi delle applicazioni 2 GB di spazio VM rimasti. La soluzione, quando possibile, è quella di allocare una volta quello che ti serve, e quindi non rilasciarlo - riutilizzalo la volta successiva.

Utilizzare DebugDiag per analizzare il processo. Guarda come gli indirizzi VM si avvicinano gradualmente fino a 2 GB. Quindi apporta una modifica che impedisca che ciò accada.

0

Sono d'accordo con alcuni degli altri poster qui che si potrebbe voler provare e usare trucchi per lavorare con .NET Framework invece di provare a forzare a lavorare con voi tramite GC.Collect.

È possibile trovare questo Channel 9 video utile in cui vengono illustrati i modi per ridurre la pressione sul Garbage collector.