2012-06-08 19 views
14

El asignador glibc de Linux parece comportarse de manera extraña. Con suerte, alguien puede arrojar algo de luz sobre esto. Aquí está el código fuente que tengo:El asignador de Linux no libera trozos pequeños de 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 tanto:

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

corro en primer lugar, y después de que está durmiendo, veo el tamaño de la memoria residente:

Cuando puedo compilar first.cpp, y ejecutarlo, miro 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 

Aviso del tamaño de la memoria residente. En primer lugar, el tamaño de la memoria residente es 53016k. en segundo lugar, es 1024k. Primero nunca lanzó las asignaciones al kernel por alguna razón u otra.

¿Por qué el primer programa no renuncia a la memoria del kernel, pero el segundo programa sí? Entiendo que el primer programa utiliza una lista vinculada y que la lista vinculada probablemente asigna algunos nodos en la misma página que los datos que estamos liberando. Sin embargo, esos nodos deberían liberarse, ya que estamos eliminando esos nodos y luego borrando la lista vinculada. Si ejecuta cualquiera de estos programas a través de valgrind, regresa sin pérdidas de memoria. Lo que probablemente está sucediendo es que la memoria se fragmenta en first.cpp que no está en second.cpp. Sin embargo, si se libera toda la memoria de una página, ¿cómo no se devuelve esa página al kernel? ¿Qué se necesita para que la memoria sea devuelta al kernel? ¿Cómo puedo modificar first.cpp (continuar colocando los caracteres * en una lista) para que la memoria se renuncie al kernel?

+2

Uso encoge para caber, describe [aquí] (http://stackoverflow.com/questions/5834754/stddeque-does-not-release-memory-until-program-exits). En este caso, haga 'std :: list () .swap (ptrs)'. – jxh

+1

Me temo que no es otra cosa fuera de lugar aquí ... Aquí está mi nuevo programa: int main() {{ std :: lista PAD; para (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(); std :: list () .swap (ptrs); } sueño (100); return 0; } ps ejecución tiene el mismo resultado: davidw 9961 0,0 0,3 64344 53016 pts/4 S doce y treinta y uno 0:00 ./first – user1418199

+0

por qué se esta etiquetada C? –

Respuesta

3

Normalmente, la memoria asignada por new solo se devolverá al sistema cuando finalice el proceso. En el segundo caso, sospecho que libc está usando un asignador especial para bloques continuos muy grandes, que sí lo devuelve, pero me sorprendería mucho si se devolviera alguno de sus new char[1024], y en muchos Unices, incluso el bloque grande ganado no será devuelto

5

Mantiene los trozos más pequeños disponibles en caso de que los solicite nuevamente. Es una optimización simple de almacenamiento en caché, y no un comportamiento del que preocuparse.

15

Este comportamiento es intencional, hay un umbral ajustable que glibc usa para decidir si realmente devolverá la memoria al sistema o si se almacenará en caché para su reutilización posterior. En su primer programa, usted hace muchas asignaciones pequeñas con cada push_back y esas pequeñas asignaciones no son un bloque contiguo y, presumiblemente, están por debajo del umbral, por lo que no se devuelven al sistema operativo.

Calling malloc_trim(0) después de borrar la lista debe causar glibc para inmediatamente volver la región superior la mayor parte de memoria libre para el sistema (lo que requiere una llamada sbrk sistema de próxima vez que se necesite memoria .)

Si realmente necesita para anular el comportamiento por defecto (que no lo recomendaría a menos de perfiles revela que en realidad ayuda), entonces usted probablemente tendrá que usar strace y/o experimentar con mallinfo a ver lo que está sucediendo realmente en su programa , y tal vez usando mallopt a ajuste el umbral para devolver la memoria al sistema.

2

(Editado por mi respuesta, ya que en realidad no hay ningún problema aquí.)

Como se ha señalado, no es realmente un problema aquí. Johnathon Wakely golpea el clavo en la cabeza.

Cuando la utilización de la memoria no es lo que espero que sea en Linux, generalmente comienzo mi análisis con la herramienta mtrace y analizo el archivo /proc/self/maps.

mtrace se usa al poner su código entre corchetes en dos llamadas, una para iniciar la traza y otra que la finaliza.

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

Los mtrace llamadas sólo están activos si la variable de entorno se establece MALLOC_TRACE. Especifica el nombre del archivo para la salida de registro mtrace. Esta salida de registro se puede analizar para detectar fugas de memoria. Se puede usar un programa de línea de comando llamado mtrace para analizar la salida.

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

/proc/self/maps El archivo proporciona una lista de las regiones de memoria asignada en uso por el programa actual, incluidas las regiones anónimos. Puede ayudar a identificar regiones que son particularmente grandes, y luego se necesita investigación adicional para determinar a qué región está asociada. A continuación se muestra un programa simple para volcar el archivo /proc/self/maps a otro archivo.

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(); 
} 
Cuestiones relacionadas