2011-11-08 17 views
6

Tengo el siguiente código para probar cancelación de asignación de memoria utilizando un recipiente std :: lista:por qué el compilador difiere std :: list desasignación?

#include <iostream> 
#include <list> 
#include <string> 

#include <boost/bind.hpp> 

/* count of element to put into container 
*/ 
static const unsigned long SIZE = 50000000; 

/* element use for test 
*/ 
class Element 
{ 
public: 
    Element() 
    : mId(0) 
    {} 
    Element(long id) 
    : mId(id) 
    {} 
    virtual ~Element() 
    { 
    } 
    inline long getId() const 
    { 
     return this->mId; 
    } 

    inline bool operator<(const Element & rightOperand) const 
    { 
     return this->mId < rightOperand.mId; 
    } 

    inline bool isEven() const 
    { 
     return 0 == (this->mId & 1); 
    } 

private: 
    long mId; 
}; 

typedef std::list<Element> Elements; 

int main(int argc, char * argv[]) 
{ 
    std::string dummy; 
    { 
     Elements elements; 

     std::cout << "Inserting "<< SIZE << " elements in container" << std::endl; 
     std::cout << "Please wait..." << std::endl; 

     /* inserting elements 
     */ 
     for(long i=0; i<SIZE; ++i) 
     { 
      elements.push_back(i); 
     } 

     std::cout << "Size is " << elements.size() << std::endl; 
     std::getline(std::cin, dummy); // waiting user press enter 

     /* remove even elements 
     */ 
     elements.remove_if(boost::bind(& Element::isEven, _1)); 

     std::cout << "Size is " << elements.size() << std::endl; 
     std::getline(std::cin, dummy); 
    } 

    std::getline(std::cin, dummy); 

    return 0; 
} 

La ejecución de este código me da el siguiente perfil de memoria:

MemoryProfile

Parece que es gcc posponiendo la desasignación y en mi programa de prueba, al final no tiene otra opción y desasignar la memoria antes de volver a la línea de comando.

¿Por qué el deslocalización ocurre tan tarde?

He intentado con un vector probar otro contenedor y los trucos de contracción funcionan y desasignan la memoria liberada cuando la esperaba.

gcc 4.5.0, Linux 2.6.34

+0

¿Cómo se mide el consumo de memoria? – NPE

Respuesta

8

mayoría de los sistemas operativos (incluyendo Linux) sólo permiten procesos para asignar bastante grandes trozos de memoria, y cuáles no muy pequeños; incluso si es posible, es más costoso realizar asignaciones pequeñas que pocas grandes. En general, la biblioteca de C++ obtendrá grandes porciones del sistema operativo y usará su propio administrador de montón para asignar pequeñas porciones de ellas al programa. Por lo general, los trozos grandes no se devolverán al sistema operativo una vez que se hayan dividido así; seguirán asignados al proceso y se reutilizarán para futuras asignaciones.

list asigna memoria en trozos pequeños (uno por nodo), por lo que la memoria asignada no se liberará hasta que finalice el programa. vectorpuede obtener su memoria como una única asignación grande directamente desde el sistema operativo, en cuyo caso se liberará cuando se desasigne.

+0

Linux no establece ningún límite inferior en el tamaño de una asignación. Por otro lado, la asignación a nivel de sistema puede ser relativamente costosa, por lo que cualquier tiempo de ejecución bien escrito evitará hacerlo para bloques pequeños. –

+0

@JamesKanze: Puedo estar equivocado, pero no creo que pueda asignar menos de una página (generalmente 8k) a la vez. En cualquier caso, tiene razón en que sería muy costoso hacer muchas pequeñas asignaciones del sistema, por lo que no le gustaría, incluso si pudiera. –

+0

En mi sistema Vector libere la memoria al salir del alcance – Nelstaar

2

¿Qué muestra exactamente su gráfico? El destructor de std::list desasigna toda la memoria, de modo que se puede reutilizar en otro lugar del programa , pero la desasignación no devolverá necesariamente la memoria al sistema , donde otros procesos pueden utilizarla. Históricamente, al menos, en Unix, una vez que la memoria ha sido asignada a un proceso, permanece con ese proceso hasta que el proceso finaliza. Los algoritmos más nuevos de pueden devolver la memoria al sistema operativo, pero incluso y cosas como la fragmentación pueden impedir que lo haga — si asigna , y luego libera un bloque realmente grande, puede devolverse, pero si Asignar una gran cantidad de pequeños bloques (que es lo que hace std::list), el tiempo de ejecución asignará bloques grandes desde el sistema operativo, que parcelas; tales bloques grandes no pueden devolverse hasta que se hayan liberado todos los bloques pequeños , y es probable que no se devuelvan incluso entonces.

1

Depende de cómo se mida el uso de la memoria. Si está midiendo la memoria de proceso en uso, esto es lo que en realidad podría esperar.

Es bastante común que un programa solicite memoria y la asigne desde el entorno de control (como un sistema operativo) al proceso pero, cuando se libera la memoria, no necesariamente se quita del proceso . Se puede devolver a un grupo libre dentro del proceso.

Esta era la forma en que la asignación solía funcionar en los viejos tiempos. Una llamada a brk o sbrk aumentaría el tamaño del montón al darle más memoria al proceso. Esa memoria se agregaría a la arena desde la cual se cumplieron las llamadas malloc.

Pero, free devolvería la memoria a la arena, no necesariamente de vuelta al sistema operativo.

Imagino que algo similar está sucediendo en este caso.

1

Su perfil de memoria es en realidad el consumo de espacio de direcciones del proceso (la suma de mmap -ed páginas, como por ejemplo dado por /proc/self/statm o /proc/self/maps desde el punto de vista del proceso).

Pero cuando una C o una memoria de liberación función de C++ (previamente asignado con malloc o new, que están utilizando mmap para conseguir la memoria del kernel de Linux) utilizando free o delete, no se da de nuevo al sistema (usando munmap - porque eso sería [problemas de fragmentación] demasiado lentos o poco práctico -. pero sólo mantuvo como reutilizable para el futuro malloc o new

Así cancelación de asignación fue así cuando lo solicite free pero el recuerdo no se le da de nuevo al sistema, pero mantiene durante reutilización futura.

Si realmente desea que se devuelva la memoria, escriba su propio asignador (arriba de mmap y munmap) pero generalmente no vale la pena el esfuerzo.

Tal vez usando Boehm's GC podría ayudar (es muy útil, para no molestar sobre free -ing o delete -ing) si se llama explícitamente GC_gcollect() (pero no estoy seguro de ello), pero que realmente no debería cuidar mucho .

Y su pregunta no está técnicamente relacionada con gcc (sería lo mismo con otro compilador de C++). Está relacionado con malloc y new (es decir, con las bibliotecas C++ estándar C &) en Linux.

Cuestiones relacionadas