2012-05-17 14 views
9

Sto lavorando su una grande applicazione server scritta usando C++. Questo server deve essere eseguito possibilmente per mesi senza riavviare. La frammentazione è già un problema sospetto qui, dal momento che il nostro consumo di memoria aumenta nel tempo. Finora la misura è stata quella di confrontare i byte privati ​​con i byte virtuali e analizzare la differenza in questi due numeri.Pensare alla frammentazione della memoria mentre codifichi: Ottimizzazione prematura o no?

Il mio approccio generale alla frammentazione è di lasciarlo all'analisi. Ho lo stesso modo di pensare ad altre cose come le prestazioni generali e le ottimizzazioni della memoria. Devi eseguire il backup delle modifiche con analisi e prove.

Mi sto notando molto durante le revisioni o le discussioni sul codice, che la frammentazione della memoria è una delle prime cose che si presentano. È quasi come se ci fosse una grande paura ora e c'è una grande iniziativa per "prevenire la frammentazione" prima del tempo. Sono richieste modifiche al codice che sembrano favorevoli alla riduzione o alla prevenzione dei problemi di frammentazione della memoria. Io tendo ad essere in disaccordo con queste persone sin da quando mi sembrano un'ottimizzazione prematura. Vorrei sacrificare la pulizia del codice/la leggibilità/la manutenibilità/ecc. per soddisfare questi cambiamenti.

Ad esempio, prendiamo il seguente codice:

std::stringstream s; 
s << "This" << "Is" << "a" << "string"; 

Sopra, il numero di allocazioni del stringstream fa qui è indefinito, potrebbe essere 4 allocazioni, o solo 1 allocazione. Quindi non possiamo ottimizzare basandoci solo su questo, ma il consenso generale è quello di utilizzare un buffer fisso o in qualche modo modificare il codice per utilizzare potenzialmente meno allocazioni. Non vedo davvero che lo stringstream si espanda qui come un enorme contributore ai problemi di memoria, ma forse mi sbaglio.

proposte di miglioramento generale di codice di cui sopra sono lungo le linee di:

std::stringstream s; 
s << "This is a string"; // Combine it all to 1 line, supposedly less allocations? 

C'è anche una spinta enorme per utilizzare lo stack sopra il mucchio dove mai possibile.

È possibile essere preventivi sulla frammentazione della memoria in questo modo, oppure si tratta semplicemente di un falso senso di sicurezza?

+4

Penso che un modo semplice per giudicare sia questo: ti preoccupi di due cose nei programmi: correttezza dell'implementazione e efficienza di implementazione. Vogliamo tutti il ​​massimo in entrambe le categorie, ma in pratica è meglio concentrarsi sulla correttezza rispetto all'efficienza, perché il programma sbagliato più efficiente del mondo è ancora sbagliato e ancora inutile. Dovresti concentrarti su entrambi al meglio delle tue capacità; * l'ottimizzazione prematura significa semplicemente concentrarsi più sull'efficienza che sulla correttezza *. Se hai la capacità di renderlo efficiente senza sacrificare la correttezza, dovresti assolutamente farlo! – GManNickG

+3

'La frammentazione è già un problema sospetto qui, dal momento che il nostro consumo di memoria aumenta col passare del tempo. Le vecchie perdite di memoria sarebbero un sospetto molto più probabile a mio parere - potrebbe voler escluderle prima (e con un controllore di perdita di memoria come valgrind o drmemory è anche molto più facile che scrutare l'intero codice in base alle fonti di frammentazione) – smocking

risposta

14

Non è l'ottimizzazione prematura se si sa in anticipo che è necessario essere a basso frammentazione e aver misurato in anticipo che la frammentazione è un problema reale per voi e sapere in anticipo quali segmenti del codice sono pertinente. Le prestazioni sono un requisito, ma l'ottimizzazione cieca è negativa in qualsiasi situazione.

Tuttavia, l'approccio superiore consiste nell'utilizzare un allocatore personalizzato senza frammentazione, come il pool di oggetti o la memoria arena, che non garantisce alcuna frammentazione. Ad esempio, in un motore fisico, è possibile utilizzare un'arena di memoria per tutte le assegnazioni per tick e svuotarla alla fine, che non è solo ridicolmente veloce (anche più veloce di _alloca su VS2010) ma anche estremamente efficiente in termini di memoria e bassa frammentazione.

+0

In qualità di sviluppatore di giochi, gli allocatori e i pool personalizzati sono molto comuni e generalmente li sviluppiamo fin dall'inizio. Non c'è ragione per non sapere quando avrai bisogno di loro. Rende anche le cose molto più semplici quando si determinano i budget per vari sottosistemi. –

+0

Immagino che il consenso generale qui sia che, poiché riteniamo che la frammentazione sia già un problema, le allocazioni future amplificano il problema. A tale riguardo, ritengono che l'ottimizzazione non venga effettuata "alla cieca", tuttavia ritengo che ogni caso sia specifico per il contesto e l'esistenza di problemi di frammentazione potrebbe non essere rilevante per le assegnazioni future. Quali sono i tuoi pensieri? –

+0

@RobertDailey: vorresti comunque identificare quali allocazioni causano effettivamente la frammentazione. Non c'è motivo di credere che l'allocazione aggiuntiva casuale X farà un briciolo di differenza, proprio come se si aggiungesse una funzione casuale X e la si chiami una volta, sarà irrilevante anche se si è già vincolati alla CPU. – Puppy

1

Penso che sia più di una buona pratica di una ottimizzazione prematura. Se si dispone di una suite di test, è possibile creare un set di test della memoria per eseguire e misurare la memoria, le prestazioni e così via, ad esempio nel periodo notturno. Puoi leggere i rapporti e correggere alcuni errori se possibile.

Il problema con piccole ottimizzazioni è modificare il codice per qualcosa di diverso ma con la stessa logica di business. Come usare un ciclo inverso per perché è più veloce del normale per. il tuo test di unità probabilmente ti guida a ottimizzare alcuni punti senza effetti collaterali.

6

È assolutamente ragionevole considerare la frammentazione della memoria a livello algoritmico. È anche ragionevole allocare nello stack piccoli oggetti di dimensioni fisse per evitare il costo di un'allocazione dell'heap non necessaria e gratuita. Tuttavia, definirò sicuramente la linea in tutto ciò che rende il codice più difficile da eseguire il debug, l'analisi o la manutenzione.

Sarei anche preoccupato che ci siano molti suggerimenti che sono semplicemente sbagliati. Probabilmente 1/2 delle cose che la gente dice in genere dovrebbe essere fatto "per evitare la frammentazione della memoria" probabilmente non ha alcun effetto e una percentuale considerevole del resto è probabilmente dannosa.

Per la maggior parte realistiche applicazioni di tipo server a lungo termine su hardware di elaborazione moderno tipico, la frammentazione della memoria virtuale spazio utente non sarà un problema con la codifica semplice e diretta.

+0

Il tuo secondo commento è molto importante. Ciò che causa la frammentazione non è sempre ovvio, né la soluzione. –

-3

Un altro punto che vorrei menzionare è: perché non provi una sorta di garbage collector. puoi invocarlo dopo una certa soglia o dopo un certo periodo di tempo. il garbage collector raccoglierà automaticamente la memoria non utilizzata dopo una certa soglia.

Anche per quanto riguarda la frammentazione, prova ad allocare qualche tipo di storage per diversi tipi di oggetti e gestiscili direttamente nel tuo codice.

ie se si hanno 5 tipi di oggetti (di classe A, B, C, D ed E). è possibile allocare spazio all'inizio per dire 1000 oggetti di ogni tipo all'inizio, ad esempio cacheA, cacheB ... cacheE.

Quindi, eviterete molti richiami di malloc e nuovi così come la frammentazione sarà molto meno. Anche il codice sarà leggibile come prima, dal momento che è necessario implementare qualcosa come myAlloc, che verrà allocata dalla cacheA, dalla cacheB ecc ...

1

Fare molta attenzione alla frammentazione della memoria prima di incontrarla effettivamente è chiaramente l'ottimizzazione prematura; Non prenderei troppo in considerazione nella progettazione iniziale. Cose come il buon incapsulamento sono più importanti (dato che ti permetteranno di cambiare la rappresentazione della memoria più tardi, se necessario).

D'altra parte, è buona progettazione per evitare allocazioni non necessarie e per utilizzare variabili locali anziché allocazione dinamica quando possibile. Non solo per ragioni di frammentazione, ma anche per ragioni di semplicità del programma. Il C++ tende a preferire la semantica del valore in generale, ei programmi che utilizzano la semantica del valore (copia e assegnazione) sono più naturali di quelli che usano la semantica di riferimento (allocazione dinamica e puntatori di passaggio in giro).

0

Penso che non dovresti risolvere il problema della frammentazione prima di incontrarlo effettivamente, ma allo stesso tempo il tuo software dovrebbe essere progettato per consentire una facile integrazione di tale soluzione per il problema della frammentazione della memoria. E poiché la soluzione è un allocatore di memoria personalizzato, significa che inserirne uno nel tuo codice (operatore new/delete e classi Allocator per i tuoi contenitori) dovrebbe essere fatto cambiando una riga di codice da qualche parte nel tuo file config.h, e assolutamente non da passando attraverso tutte le istanze di tutti i contenitori e così via. Un altro punto a sostegno di ciò è che il 99% di tutto il software attuale è multi-thread e l'allocazione della memoria da thread diversi porta a problemi di sincronizzazione ea volte false-sharing. E la risposta a questi problemi è di nuovo l'allocatore di memoria personalizzato.

Quindi, se il tuo progetto supporta l'allocatore personalizzato, non dovresti accettare le modifiche al codice che ti vengono vendute come "liberazione della frammentazione", non finché non profili la tua app e vedi di persona che la patch diminuisce davvero il numero di DTLB o LLC non riescono a comprimere meglio i dati. Se, tuttavia, la progettazione non consente l'allocatore personalizzato, questo dovrebbe essere implementato come primo passo prima di eseguire qualsiasi altra modifica della "frammentazione della memoria".

Da quello che ricordo del design interno, l'allocatore scalabile di Threading Building Blocks può essere provato per entrambi: aumentare la scalabilità di allocazione della memoria e ridurre la frammentazione della memoria.

Un altro piccolo punto: l'esempio si sta facendo con l'allocazione stringstream (s) e la politica per il confezionamento di assegnazioni insieme per quanto possibile - la mia comprensione è che in alcuni casi questo porterà alla frammentazione della memoria invece di risolvere questo problema. Il raggruppamento di tutte le allocazioni consente di richiedere blocchi di memoria contigui di grandi dimensioni, che potrebbero finire per essere dispersi e quindi altre richieste di grandi dimensioni simili non saranno in grado di colmare le lacune.

Problemi correlati