2012-06-08 9 views
14

L'allocatore di glibc di Linux sembra comportarsi in modo strano. Spero che qualcuno possa far luce su questo. Ecco il file di origine che ho:L'allocatore di Linux non rilascia piccoli pezzi di memoria

first.cpp:

#include <unistd.h> 
#include <stdlib.h> 
#include <list> 
#include <vector> 

int main() { 

    std::list<char*> ptrs; 
    for(size_t i = 0; i < 50000; ++i) { 
    ptrs.push_back(new char[1024]); 
    } 
    for(size_t i = 0; i < 50000; ++i) { 
    delete[] ptrs.back(); 
    ptrs.pop_back(); 
    } 

    ptrs.clear(); 

    sleep(100); 

    return 0; 
} 

second.cpp:

#include <unistd.h> 
#include <stdlib.h> 
#include <list> 

int main() { 

    char** ptrs = new char*[50000]; 
    for(size_t i = 0; i < 50000; ++i) { 
    ptrs[i] = new char[1024]; 
    } 
    for(size_t i = 0; i < 50000; ++i) { 
    delete[] ptrs[i]; 
    } 
    delete[] ptrs; 

    sleep(100); 

    return 0; 
} 

compilo entrambi:

 
$ g++ -o first first.cpp 
$ g++ -o second second.cpp 

corro prima, e dopo aver dormito, vedo la dimensione della memoria del residente:

Quando compilo first.cpp ed eseguirlo, guardo memoria con ps:

$ ./first& 
$ ps aux | grep first 
davidw 9393 1.3 0.3 64344 53016 pts/4 S 23:37 0:00 ./first 


$ ./second& 
$ ps aux | grep second 
davidw 9404 1.0 0.0 12068 1024 pts/4 S 23:38 0:00 ./second 

Avviso la dimensione della memoria residente. In primo luogo, la dimensione della memoria residente è 53016k. in secondo luogo, è 1024k. Per prima cosa non ho mai rilasciato le allocazioni al kernel per una ragione o per l'altra.

Perché il primo programma non rilascia la memoria nel kernel, ma il secondo programma funziona? Comprendo che il primo programma utilizza un elenco collegato e l'elenco collegato probabilmente alloca alcuni nodi nella stessa pagina dei dati che stiamo liberando. Tuttavia, questi nodi dovrebbero essere liberati, dato che stiamo spegnendo quei nodi, quindi cancellando l'elenco collegato. Se si esegue uno di questi programmi tramite valgrind, viene restituito senza perdite di memoria. Quello che probabilmente sta succedendo è che la memoria viene frammentata in first.cpp che non è in second.cpp. Tuttavia, se viene liberata tutta la memoria su una pagina, in che modo la pagina non viene restituita al kernel? Cosa ci vuole perché la memoria si ritrasferisca al kernel? Come posso modificare first.cpp (continuando a mettere il char * in una lista) in modo che la memoria venga abbandonata al kernel.

+2

Usa ridursi per adattarsi, descritto [qui] (http://stackoverflow.com/questions/5834754/stddeque-does-not-release-memory-until-program-exits). In questo caso, fare 'std :: list () .swap (ptrs)'. – jxh

+1

Temo che ci sia qualcosa d'altro che non va qui ... Ecco il mio nuovo programma: int main() {{ std :: list PTR; per (size_t i = 0; i <50000; ++ i) { ptrs.push_back (nuovo carattere [1024]); } per (size_t i = 0; i <50000; ++ i) { delete [] ptrs.back(); ptrs.pop_back(); } ptrs.clear(); std :: list () .swap (ptrs); } sleep (100); ritorno 0; } ps esecuzione ha lo stesso risultato: davidw 9961 0.0 0.3 64344 53016 p.ti/4 S 00:31 00:00 ./first – user1418199

+0

perché questa etichettato C? –

risposta

3

In genere, la memoria allocata da new verrà restituita al sistema solo al termine del processo. Nel secondo caso, sospetto che libc stia utilizzando uno speciale allocatore per blocchi continui molto grandi, il che lo restituisce, ma sarei molto sorpreso se qualcuno dei tuoi new char[1024] fosse stato restituito, e su molti Unix, anche il grande blocco vinto essere restituito

5

Mantiene i pezzi più piccoli disponibili nel caso in cui vengano richiesti nuovamente. È una semplice ottimizzazione del caching e non un comportamento di cui preoccuparsi.

15

Questo comportamento è intenzionale, esiste una soglia sintonizzabile che glibc utilizza per decidere se restituire effettivamente la memoria al sistema o se memorizzarla nella cache per un successivo riutilizzo. Nel tuo primo programma fai molte piccole allocazioni con ogni push_back e quelle piccole allocazioni non sono un blocco contiguo e sono presumibilmente al di sotto della soglia, quindi non tornare al sistema operativo.

Calling malloc_trim(0) dopo aver eliminato la lista dovrebbe causare glibc per immediatamente tornare la regione più in alto di memoria libera per il sistema (che richiede una chiamata di sistema sbrk prossima volta è necessaria la memoria .)

Se avete veramente bisogno di ignorare il comportamento predefinito (che io non lo consiglio a meno che il profiling rivela che in realtà aiuta) allora probabilmente si dovrebbe utilizzare strace e/o sperimentare con mallinfo per vedere cosa sta realmente accadendo nel vostro programma e forse usando mallopt a regolare la soglia per la restituzione di memoria al sistema.

2

(editing giù la mia risposta, dal momento che non c'è davvero alcun problema qui.)

Come è stato notato, non c'è davvero un problema qui. Johnathon Wakely colpisce il chiodo sulla testa.

Quando l'utilizzo della memoria non è quello che mi aspetto che sia su Linux, di solito avvio la mia analisi utilizzando lo strumento e analizzando il file /proc/self/maps.

mtrace viene utilizzato dal bracketing del codice attorno a due chiamate, una per avviare la traccia e una che la interrompe.

mtrace(); 
    { 
     // do stuff 
    } 
    muntrace(); 

I mtrace chiamate sono attivi solo se la variabile MALLOC_TRACE ambiente è impostata. Specifica il nome del file per l'output di registrazione mtrace. Questo output di registrazione può quindi essere analizzato per perdite di memoria. Un programma a riga di comando chiamato mtrace può essere utilizzato per analizzare l'output.

$ MALLOC_TRACE=mtrace.log ./a.out 
$ mtrace ./a.out mtrace.log 

Il file /proc/self/maps fornisce un elenco delle aree di memoria mappati in uso da parte del programma in corso, comprese le regioni anonimi. Può aiutare a identificare le regioni che sono particolarmente grandi, e quindi è necessario un ulteriore investigazione per determinare a quale regione è associata. Di seguito è riportato un semplice programma per scaricare il file /proc/self/maps in un altro file.

void dump_maps (const char *outfilename) { 
    std::ifstream inmaps("/proc/self/maps"); 
    std::ofstream outf(outfilename, std::ios::out|std::ios::trunc); 
    outf << inmaps.rdbuf(); 
} 
Problemi correlati