2011-10-14 4 views
11

Ho un'app di console di Windows che dovrebbe funzionare senza riavvii per giorni e mesi. L'app recupera "lavoro" da un MSMQ e lo elabora. Ci sono 30 thread che elaborano un blocco di lavoro contemporaneamente.Oggetto heap e stringa di grandi dimensioni Oggetti provenienti da una coda

Ogni blocco di lavoro proveniente da MSMQ è di circa 200 kb, la maggior parte dei quali è allocata in un singolo oggetto String.

Ho notato che dopo l'elaborazione di circa 3-4 migliaia di questi blocchi di lavoro il consumo di memoria dell'applicazione è ridicolmente alto consumando 1 - 1,5 GB di memoria.

Eseguo l'app tramite un profiler e ho notato che la maggior parte di questa memoria (forse un ingaggio) non è in uso nell'heap di oggetti grandi ma la struttura è frammentata.

Ho trovato che il 90% di questi byte inutilizzati (garbage collection) sono stati assegnati in precedenza a String. Ho iniziato a sospettare che le stringhe provenienti da MSMQ siano state allocate, utilizzate e quindi deallocate e siano quindi la causa della frammentazione.

Capisco che cose come GC.Collect (2 o GC.Max ...) non siano d'aiuto dato che gc l'heap di oggetti grandi ma non lo compattano (che è il problema qui). Quindi penso che quello di cui ho bisogno è di mettere in cache queste stringhe e riutilizzarle in qualche modo ma, dal momento che le stringhe sono immutabili, dovrei usare StringBuilder.

La mia domanda è: è comunque necessario non modificare la struttura sottostante (ad esempio utilizzando MSMQ poiché questo è qualcosa che non posso cambiare) ed evitare comunque di inizializzare una nuova stringa ogni volta per evitare di frammentare il LOH?

Grazie, Yannis

UPDATE: A proposito di come questi pezzi "lavoro" sono attualmente recuperati

Attualmente questi sono memorizzati come oggetti WorkChunk nel MSMQ. Ciascuno di questi oggetti contiene una stringa denominata Contenuto e un'altra stringa denominata Intestazioni. Questi sono dati testuali reali. Posso cambiare la struttura di archiviazione in qualcos'altro se necessario e potenzialmente il meccanismo di archiviazione sottostante se necessario per qualcos'altro rispetto a un MSMQ.

Sul lato nodi lavoratori attualmente facciamo

WorkChunk pezzo = _Queue.Receive();

Quindi c'è poco che possiamo memorizzare in questa fase. Se abbiamo cambiato la struttura (s) in qualche modo, allora suppongo che potremmo fare un po 'di progressi. In ogni caso, dovremo risolvere questo problema, quindi faremo tutto il necessario per evitare di buttare fuori mesi di lavoro.

AGGIORNAMENTO: Ho continuato a provare alcuni dei suggerimenti riportati di seguito e ho notato che questo problema non può essere riprodotto sul mio computer locale (con l'app Windows 7 x64 e 64 bit). questo rende le cose molto più difficili - se qualcuno sa perché allora sarebbe davvero utile ripubblicare questo problema localmente.

+0

Come si ricevono quelle stringhe? Quando sono archi sei bloccato. Vengo da un flusso o da un byte [] potresti avere alcune opzioni. –

+0

Ciao Henk - Dai un'occhiata all'aggiornamento per avere più informazioni su questi pezzi di lavoro – Yannis

+0

Ma è un problema reale? 1.5 GB su un PC a 64 bit con> = 8 GB di RAM dovrebbe essere in grado di continuare. –

risposta

4

Il problema sembra essere dovuto all'allocazione della memoria sull'heap di un oggetto di grandi dimensioni: l'heap di oggetti di grandi dimensioni non è compattato e quindi può essere una fonte di frammentazione. C'è un buon articolo qui che entra più nel dettaglio tra cui alcuni passi di debug che è possibile seguire per confermare che la frammentazione della grande mucchio oggetto che sta accadendo:

Large Object Heap Uncovered

Si sembrano avere due tre soluzioni:

  1. modificare la vostra applicazione per eseguire l'elaborazione in pezzi/stringhe più brevi, in cui ogni pezzo è più piccolo di 85.000 byte - questo evita l'assegnazione di oggetti di grandi dimensioni.
  2. Modificare l'applicazione per allocare alcuni blocchi di memoria di grandi dimensioni e riutilizzare quei blocchi copiando i nuovi messaggi nella memoria allocata. Vedi Heap fragmentation when using byte arrays.
  3. Lascia le cose come sono - Finché non si verificano eccezioni di memoria esaurita e l'applicazione non interferisce con altre applicazioni in esecuzione sul sistema, è probabile che si lascino le cose come sono.

sua importante comprendere la distinzione tra memoria virtuale e memoria fisica - anche se il processo utilizza una grande quantità di memoria virtuale, se il numero di oggetti allocato è relativamente bassa, allora CaM essere che la memoria fisica l'utilizzo di tale processo è basso (la memoria non utilizzata è paginata su disco), il che ha un impatto limitato su altri processi sul sistema. Potresti anche scoprire che l'opzione "Accumulazione VM" aiuta a leggere l'articolo "Oggetto di grandi dimensioni scoperto" per ulteriori informazioni.

O una modifica comporta la modifica dell'applicazione per eseguire alcuni o tutti i suoi processi utilizzando array di byte e sottostringhe brevi invece di una singola stringa di grandi dimensioni - quanto sarà difficile dipenderà da che tipo di elaborazione è che stai facendo

+0

Grazie Justin. Il problema è che queste stringhe provengono da un sistema diverso attraverso una coda di messaggi. Quindi non posso dire "prendi metà di quel pezzo di lavoro" al momento, a meno che non cambi la struttura generale dello storage - e credo che sia dove ho bisogno di idee e suggerimenti – Yannis

+0

@annivi Se vuoi modificare la tua applicazione allora sembra così - per suggerimenti su come si potrebbe voler fare questo un po 'più di dettaglio sul tipo di elaborazione che viene fatto è probabilmente necessario. Hai visto la mia ultima modifica? Dovresti considerare che questo comportamento che stai vedendo potrebbe essere perfetto (purché non si ottengano le eccezioni OOM, si tratta di un processo a 32 o 64 bit?) – Justin

+0

Justin - Questo è un processo a 64 bit e il risultato è che il computer (Windows 2008 Server) rallenta a causa di un numero eccessivo di pagine. Questo ha senso. Lasciatemelo chiedere: se cambio la proprietà Contenuto stringa in char [] [] che contiene array di char di pezzi di char di 85k (il limite per mettere qualcosa sul LOH) - sarebbe di aiuto? – Yannis

1

Forse è possibile creare un pool di oggetti stringa che è possibile utilizzare durante l'elaborazione del lavoro, quindi tornare al termine.

Una volta che un oggetto di grandi dimensioni è stato creato nel LOH, non può essere rimosso (AFAIK), quindi se non puoi evitare di creare questi oggetti, il piano migliore è riutilizzarli.

Se è possibile modificare il protocollo su entrambe le estremità, ridurre la stringa "Contenuto" in un gruppo di più piccoli (< 80k ciascuno) dovrebbe impedirne l'archiviazione nel LOH.

+0

Questo è ciò che l'OP già ha detto. Ma come riusare una stringa? –

+0

Aggiunta una modifica al post originale con ulteriori informazioni – Yannis

+0

Tony - il problema è la serializzazione di questi contenuti e la deserializzazione all'altra estremità. Qualsiasi cosa faccia questo oggetto conterrà questi "contenuti" in un modo o nell'altro, anche in piccoli pezzi. – Yannis

2

Quando c'è una frammentazione sul LOH, significa che ci sono oggetti assegnati su di esso. Se riesci ad annullare il ritardo, puoi attendere una volta finché tutte le attività in corso non sono terminate e chiamare GC.Collect(). Quando non ci sono oggetti di grandi dimensioni referenziati, saranno tutti raccolti, rimuovendo efficacemente la frammentazione del LOH. Ovviamente questo funziona solo se (al massimo) tutti gli oggetti di grandi dimensioni non sono referenziali.

Inoltre, anche il passaggio a un sistema operativo a 64 bit può essere d'aiuto, poiché la memoria insufficiente a causa della frammentazione è molto meno probabile che sia un problema nei sistemi a 64 bit, perché lo spazio virtuale è quasi illimitato.

+0

Steven Penso che tu abbia torto poiché la frammentazione non significa che gli oggetti ci sono (nel LOH) ma erano una volta lì e alla fine sono stati deselezionati lasciando così un blocco vuoto nel LOH. Ciò significa che se c'è un chunk di 120k (diciamo) e stiamo cercando di allocare 121k, questo sarà allocato al primo blocco contiguo disponibile di 121k byte, lasciando quindi il chunk di 120k vuoto. GC.Collect() purtroppo disalloca solo gli oggetti LOH (e per quello GC.Collect (GC.MaxGeneration) è necessario) e non compatta il LOH. – Yannis

+1

Non penso che Steven stia dicendo che GC.Collect si compatta, penso che stia dicendo di chiamarlo quando hai solo pochi oggetti in movimento. In questo modo si libererà dei grandi oggetti tra i quali ci sono gli spazi che ti lasciano con una bella (ish) tabula rasa. – Joey

+1

@Yannis: Quello che sto dicendo è: un LOH vuoto non può essere frammentato. Joey lo ha riformulato bene. – Steven

0

Come utilizzare String.Intern (...) per eliminare i riferimenti duplicati. Ha una penalizzazione delle prestazioni, ma a seconda delle stringhe potrebbe avere un impatto.

+0

Funzionerebbe meglio se potessi tagliare l'intestazione e il contenuto in coppie chiave/valore, facendo .Intern su tutte le chiavi e i valori.Si finirebbe con nessun dato duplicato, ma una struttura di dati diversa, che potrebbe richiedere più elaborazione. –