Come risolvere la frammentazione della memoria

Di tanto in tanto abbiamo avuto problemi con i nostri processi server a esecuzione prolungata (in esecuzione su Windows Server 2003) che hanno generato un’eccezione a causa di un errore di allocazione della memoria. Il nostro sospetto è che queste allocazioni stiano fallendo a causa della frammentazione della memoria.

Pertanto, abbiamo esaminato alcuni meccanismi alternativi di allocazione della memoria che potrebbero aiutarci e spero che qualcuno possa dirmi il migliore:

1) Usa Windows Heap a frammentazione ridotta

2) jemalloc – come in Firefox 3

3) Malloc di Doug Lea

Il nostro processo server è sviluppato utilizzando codice C ++ multipiattaforma, quindi qualsiasi soluzione sarebbe idealmente anche multipiattaforma (i sistemi operativi * do * nix soffrono di questo tipo di frammentazione della memoria?).

Inoltre, ho ragione nel pensare che LFH sia ora il meccanismo di allocazione di memoria predefinito per Windows Server 2008 / Vista? … I miei attuali problemi “andranno via” se i nostri clienti semplicemente aggiornano il loro server os?

Innanzitutto, sono d’accordo con gli altri poster che hanno suggerito una perdita di risorse. Vuoi davvero escluderlo prima.

Si spera che il gestore di heap attualmente in uso abbia un modo per scaricare lo spazio libero totale disponibile nell’heap (attraverso tutti i blocchi liberi ) e anche il numero totale di blocchi su cui è suddiviso. Se la dimensione media dei blocchi liberi è relativamente piccola rispetto allo spazio libero totale nell’heap, si verifica un problema di frammentazione. In alternativa, se è ansible scaricare la dimensione del blocco libero più grande e confrontarla con lo spazio libero totale, ciò comporterà la stessa cosa. Il blocco libero più grande sarebbe piccolo rispetto allo spazio libero totale disponibile su tutti i blocchi se si sta eseguendo la frammentazione.

Per essere molto chiari su quanto sopra, in tutti i casi si tratta di blocchi liberi nell’heap, non dei blocchi allocati nell’heap. In ogni caso, se le condizioni di cui sopra non sono soddisfatte, allora si ha una situazione di perdita di qualche tipo.

Quindi, una volta che hai escluso una perdita, potresti prendere in considerazione l’utilizzo di un allocatore migliore. Il malloc di Doug Lea suggerito nella domanda è un ottimo allocatore per applicazioni di uso generale e molto robusto il più delle volte. In altre parole, è stato testato il tempo per funzionare molto bene per la maggior parte delle applicazioni. Tuttavia, nessun algoritmo è ideale per tutte le applicazioni e qualsiasi approccio dell’algoritmo di gestione può essere rotto dalle giuste condizioni patologiche contro la sua progettazione.

Perché stai avendo un problema di frammentazione? – Le fonti di problemi di frammentazione sono causate dal comportamento di un’applicazione e hanno a che fare con tempi di allocazione molto diversi nella stessa arena della memoria. Cioè, alcuni oggetti vengono allocati e liberati regolarmente mentre altri tipi di oggetti persistono per lunghi periodi di tempo tutti nello stesso heap … pensate a quelli di durata più lunga come fori di foratura in aree più grandi dell’arena e quindi impedendo coalizione di blocchi adiacenti che sono stati liberati.

Per affrontare questo tipo di problema, la cosa migliore che puoi fare è dividere logicamente l’heap in sub-arena dove le vite sono più simili. In effetti, si desidera un heap temporaneo e un heap persistente o un heap che raggruppa oggetti di vite simili.

Alcuni altri hanno suggerito un altro approccio per risolvere il problema che è quello di tentare di rendere le dimensioni di allocazione più simili o identiche, ma questo è meno ideale perché crea un diverso tipo di frammentazione chiamata frammentazione interna – che è in effetti lo spazio sprecato che hai allocando più memoria nel blocco di cui hai bisogno.

Inoltre, con un buon allocatore di heap, come quello di Doug Lea, rendere superflue le dimensioni dei blocchi è superfluo, poiché l’allocatore eseguirà già uno schema di bucketing di due dimensioni che renderà completamente inutile la regolazione artificiale delle dimensioni di allocazione passate a malloc ( ) – in effetti, il suo gestore di heap lo fa automaticamente per te in modo molto più robusto di quanto l’applicazione sia in grado di apportare modifiche.

Penso che tu abbia erroneamente escluso una perdita di memoria troppo presto. Anche una piccola perdita di memoria può causare una grave frammentazione della memoria.

Supponendo che la tua applicazione si comporti come la seguente:
Assegna 10 MB
Assegna 1 byte
10 MB gratuiti
(oops, non abbiamo liberato l’1 byte, ma a chi importa circa 1 piccolo byte)

Sembra una perdita molto piccola, difficilmente noterete quando monitorate solo la dimensione totale della memoria allocata . Ma questa perdita alla fine farà sì che la memoria dell’applicazione assomigli a questa:
.
.
Gratuito – 10 MB
.
.
[Allocato -1 byte]
.
.
Gratuito – 10 MB
.
.
[Allocato -1 byte]
.
.
Gratuito – 10 MB
.
.

Questa perdita non verrà notata … fino a quando non si desidera allocare 11 MB
Supponendo che i tuoi minidump abbiano tutte le informazioni sulla memoria incluse, ti consiglio di usare DebugDiag per individuare possibili perdite. Nel report di memoria generato, esaminare attentamente il conteggio delle allocazioni (non le dimensioni) .

Come suggerisce, il malloc di Doug Lea potrebbe funzionare bene. È multipiattaforma ed è stato utilizzato nel codice di spedizione. Per lo meno, dovrebbe essere facile da integrare nel tuo codice per il test.

Avendo lavorato in ambienti di memoria fissa per un certo numero di anni, questa situazione è sicuramente un problema, anche in ambienti non fissi. Abbiamo scoperto che gli allocatori CRT tendono a puzzare piuttosto male in termini di prestazioni (velocità, efficienza dello spazio sprecato, ecc.). Credo fermamente che se hai un grande bisogno di un buon allocatore di memoria per un lungo periodo di tempo, dovresti scrivere il tuo (o vedere se qualcosa come dlmalloc funzionerà). Il trucco è ottenere qualcosa di scritto che funzioni con i tuoi schemi di allocazione, e questo ha più a che fare con l’efficienza della gestione della memoria come quasi qualsiasi altra cosa.

Dai una prova a dlmalloc. Ci tengo decisamente un pollice in su. È abbastanza sintonizzabile, quindi potresti essere in grado di ottenere maggiore efficienza modificando alcune delle opzioni di compilazione.

Onestamente, non dovresti dipendere da cose che “vanno via” con nuove implementazioni del sistema operativo. Un service pack, patch o un altro nuovo sistema operativo N anni dopo potrebbe peggiorare il problema. Di nuovo, per le applicazioni che richiedono un gestore di memoria affidabile, non utilizzare le versioni di magazzino disponibili con il compilatore. Trova quello che funziona per la tua situazione. Inizia con dlmalloc e sintonizzalo per vedere se riesci ad ottenere il comportamento più adatto alla tua situazione.

Puoi aiutare a ridurre la frammentazione riducendo la quantità che assegni deallocate.

Ad esempio, per un server Web che esegue uno script lato server, può creare una stringa per l’output della pagina. Invece di allocare e deallocare queste stringhe per ogni richiesta di pagina, è sufficiente mantenerne una parte, quindi la tua unica allocazione quando hai bisogno di più, ma la tua non deallocare (significa che dopo un po ‘non ottieni più nemmeno la situazione, perché hai abbastanza)

Puoi usare _CrtDumpMemoryLeaks (); per scaricare le perdite di memoria nella finestra di debug durante l’esecuzione di una build di debug, tuttavia credo che questo sia specifico per il compilatore Visual C. (è in crtdbg.h)

Sospetto una perdita prima di sospettare la frammentazione.

Per le strutture di dati ad alto consumo di memoria, è ansible passare a un meccanismo di pool di archiviazione riutilizzabile. Potresti anche essere in grado di allocare più cose nello stack rispetto all’heap, ma in termini pratici non farò un’enorme differenza.

Creerei uno strumento come valgrind o eseguirò alcune registrazioni intensive per cercare risorse che non vengono rilasciate.

@nsaners – Sono abbastanza sicuro che il problema sia dovuto alla frammentazione della memoria. Abbiamo analizzato minidump che indicano un problema quando viene allocato un grosso blocco di memoria (5-10 MB). Abbiamo anche monitorato il processo (in loco e in fase di sviluppo) per verificare la presenza di perdite di memoria: nessuna è stata rilevata (il footprint della memoria è generalmente piuttosto basso).

Il problema si verifica su Unix, anche se di solito non è così male.

L’heap a basso numero di inquadrature ci ha aiutato, ma i miei colleghi giurano su Smart Heap (è stato utilizzato per molti anni in una coppia di nostri prodotti). Sfortunatamente, a causa di altre circostanze, questa volta non è stato ansible utilizzare Smart Heap.

Osserviamo anche l’allocazione di blocchi / blocchi e cerchiamo di avere pool / strategie per l’ambito, ovvero cose a lungo termine qui, tutta la richiesta di richieste, cose a breve termine laggiù, ecc.

Come al solito, di solito si spreca memoria per guadagnare un po ‘di velocità.

Questa tecnica non è utile per un allocatore generico, ma ha il suo posto.

Fondamentalmente, l’idea è di scrivere un allocatore che restituisce memoria da un pool in cui tutte le allocazioni hanno la stessa dimensione. Questo pool non può mai divenire frammentato perché ogni blocco è buono come un altro. È ansible ridurre lo spreco di memoria creando più pool con blocchi di dimensioni diverse e selezionare il pool di dimensioni del lotto più piccolo che è ancora maggiore della quantità richiesta. Ho usato questa idea per creare allocatori eseguiti in O (1).

se parli di Win32, puoi provare a spremere qualcosa usando LARGEADDRESSAWARE. Avrai ~ 1 GB di memoria deframmentata in eccesso in modo che la tua applicazione la frammenterà più a lungo.