2015-09-16 14 views
9

Ho un'applicazione C++ in cui talvolta richiede un grande buffer di tipi POD (ad esempio un array di 25 b illion float s) da tenere in memoria in una volta in un blocco contiguo. Questa particolare organizzazione della memoria è guidata dal fatto che l'applicazione utilizza alcune API C che operano sui dati. Pertanto, una disposizione diversa (come un elenco di pezzi più piccoli di memoria come gli usi std::deque) non è fattibile.È possibile liberare parzialmente la memoria allocata dinamicamente su un sistema POSIX?

L'applicazione ha un algoritmo che viene eseguito sull'array in modalità streaming; pensare qualcosa di simile:

std::vector<float> buf(<very_large_size>); 
for (size_t i = 0; i < buf.size(); ++i) do_algorithm(buf[i]); 

Questo particolare algoritmo è la conclusione di una pipeline di fasi di lavorazione precedenti che sono state applicate al dataset. Pertanto, una volta che il mio algoritmo ha passato l'elemento i -th nella matrice, l'applicazione non ne ha più bisogno.

In teoria, quindi, ho potuto liberare quella memoria per ridurre l'ingombro di memoria della mia applicazione mentre masticava i dati. Tuttavia, fare qualcosa di simile a un realloc() (o a std::vector<T>::shrink_to_fit()) sarebbe inefficiente perché la mia applicazione avrebbe dovuto passare il suo tempo a copiare i dati non utilizzati nel nuovo spot in fase di riallocazione.

La mia applicazione funziona su sistemi operativi conformi a POSIX (ad esempio Linux, OS X). C'è un'interfaccia con la quale potrei chiedere al sistema operativo di liberare solo una regione specifica dalla parte anteriore del blocco di memoria? Questo sembrerebbe essere l'approccio più efficiente, in quanto potevo semplicemente notificare al gestore della memoria che, ad esempio, i primi 2 GB del blocco di memoria possono essere recuperati una volta che ho finito con esso.

+0

Alloca tutta la memoria in una volta all'inizio? Se sì: è necessario? Se no: hai provato i ringbuffer? – Gombat

+1

sì, un buffer circolare è ciò di cui hai bisogno. –

+0

@Gombat: avrei dovuto inserire questo dettaglio nell'OP, ma un passaggio precedente della catena di elaborazione richiede che l'intero buffer sia in memoria (come un blocco contiguo, quando viene passato a un'API C) in una volta. Altrimenti sì, un buffer circolare sarebbe sicuramente la scelta giusta. L'algoritmo a cui mi riferisco nella domanda è la conclusione di una pipeline di più fasi di elaborazione; una volta terminato, non ho più bisogno dei dati. –

risposta

4

È possibile liberare parzialmente la memoria allocata dinamicamente su un sistema POSIX?

Non puoi farlo utilizzando malloc()/realloc()/free().

Tuttavia, è possibile farlo in modalità semi-portatile utilizzando mmap() e munmap(). Il punto chiave è che se si munmap() qualche pagina, malloc() possono poi utilizzare la pagina:

  • creare una mappatura anonima utilizzando mmap();
  • successivamente chiama munmap() per le regioni che non ti servono più.

I problemi di portabilità sono:

  • POSIX non specifica mappature anonimi. Alcuni sistemi forniscono il flag MAP_ANONYMOUS o MAP_ANON. Altri sistemi forniscono file di dispositivo speciali che possono essere mappati per questo scopo. Linux fornisce entrambi.
  • Non penso che POSIX garantisca che quando si è munmap() una pagina, malloc() sarà in grado di usarlo.Ma penso che funzionerà tutti i sistemi che hanno mmap()/unmap().

Aggiornamento

Se la regione di memoria è così grande che la maggior parte delle pagine sicuramente saranno scritti da scambiare, non sarà sciolto nulla utilizzando mappature al file invece di mappature anonimi. Le mappature dei file sono specificate in POSIX.

5

Se l'intero buffer deve essere in memoria in una sola volta, probabilmente non si otterrà molto dalla liberazione parziale in seguito.

Il punto principale di questo post è fondamentalmente quello di NON dirti di fare ciò che vuoi fare, perché il sistema operativo non manterrà inutilmente la memoria della tua applicazione nella RAM se non è effettivamente necessaria. Questa è la differenza tra "utilizzo memoria residente" e "utilizzo memoria virtuale". "Resident" è ciò che è attualmente utilizzato e nella RAM, "virtuale" è l'utilizzo totale della memoria della tua applicazione. E finché la tua partizione di swap è abbastanza grande, la memoria "virtuale" è praticamente un problema. [Partendo dal presupposto che il tuo sistema non esaurirà lo spazio di memoria virtuale, il che è vero in un'applicazione a 64 bit, purché non usi centinaia di terabyte di spazio virtuale!]

Se continui a voglio farlo e voglio avere una portabilità ragionevole, ti suggerirei di costruire un "wrapper" che si comporta in modo simile allo std::vector e alloca blocchi di alcuni megabyte (o forse un paio di gigabyte) di memoria alla volta, e poi qualcosa come:

for (size_t i = 0; i < buf.size(); ++i) { 
    do_algorithm(buf[i]); 
    buf.done(i); 
} 

procedimento done sarà semplicemente controllare se il valore se i è (un elemento) oltre la fine del buffer corrente, e liberarla. [Questo dovrebbe inline bene, e produrre un piccolo overhead sul ciclo medio - assumendo che gli elementi siano effettivamente usati in ordine lineare, ovviamente].

Sarei molto sorpreso se questo ti porta a qualcosa, a meno che lo do_algorithm(buf[i]) richieda un po 'di tempo (sicuramente molti secondi, probabilmente molti minuti o persino ore). E, naturalmente, sarà di aiuto solo se effettivamente hai qualcos'altro utile da fare con quella memoria. E anche allora, il sistema operativo reclamerà la memoria che non viene utilizzata attivamente scambiandola sul disco, se il sistema ha poca memoria.

In altre parole, se si assegna 100 GB, riempirlo, lasciarlo seduto senza toccare, finirà TUTTO sul disco rigido anziché nella RAM.

Inoltre, non è affatto insolito che l'heap nell'applicazione conservi la memoria liberata e che il sistema operativo non recuperi la memoria fino alla chiusura dell'applicazione, e certamente, se vengono liberate solo parti di un'allocazione più grande, il runtime non lo rilascerà fino a quando l'intero blocco non sarà stato liberato. Quindi, come affermato all'inizio, non sono sicuro di quanto questo possa effettivamente aiutare la tua applicazione.

Come per tutto ciò che riguarda "ottimizzazione" e "miglioramenti delle prestazioni", è necessario misurare e confrontare un benchmark e vedere quanto aiuta.

2

Se si può fare a meno della comodità di std::vector (che non vi darà molto in questo caso comunque, perché non vorrete mai copiare/return/spostare quella bestia in ogni caso), è possibile fare la propria gestione della memoria. Chiedere al sistema operativo l'intera pagina di memoria (tramite mmap) e restituirli come appropriato (utilizzando munmap). Puoi dire allo mmap tramite il suo argomento pugno e il flag opzionale MAP_FIXED per mappare la pagina su un particolare indirizzo (che devi assicurarti di non occupare in altro modo, ovviamente) così puoi costruire un'area di memoria contigua. Se si assegna l'intera memoria in anticipo, questo non è un problema e lo si può fare con un singolo mmap e lasciare che il sistema operativo scelga un luogo conveniente per mapparlo. Alla fine, questo è ciò che internamente fa malloc. Per le piattaforme che non dispongono di sys/mman.h, non è difficile ricorrere all'utilizzo di malloc se si è convinti che su quelle piattaforme non si restituirà memoria in anticipo.

Sto sospettando che se le dimensioni di allocazione sono sempre multipli della dimensione della pagina, realloc sarà abbastanza intelligente da non copiare alcun dato. Dovresti provare questo e vedere se funziona (o consultare la tua documentazione di malloc) sulla tua particolare piattaforma di destinazione, però.

+0

Non c'è bisogno di '' 'MAP_FIXED'' 'qui, dato che puoi creare un'intera regione usando solo' '' mmap() '' 'call e successivamente' '' unmap() '' 'parti di esso. '' 'MAP_FIXED''' è specificato in POSIX, ma non esiste un modo portatile per determinare l'indirizzo corretto per esso. Quindi '' 'MAP_FIXED''' aggiunge problemi di portabilità e non risolve i problemi esistenti (hai ancora bisogno di mappature anonime non portabili). – gavv

+0

@ g-v Sì, penso di averlo risolto nella parte "Se si alloca l'intera memoria in anticipo, ...". – 5gon12eder

+0

Sì, capisco. Solo chiarimenti relativi alla portabilità. – gavv