free - long - ¿Las implementaciones de malloc devolverán la memoria de libre acceso al sistema?
tag title (7)
Tengo una aplicación de larga vida con asignación de memoria frecuente-desasignación. ¿Alguna implementación de malloc devolverá la memoria liberada al sistema?
¿Cuál es, a este respecto, el comportamiento de:
- ptmalloc 1, 2 (glibc por defecto) o 3
- dlmalloc
- tcmalloc (google threaded malloc)
- solaris 10-11 default malloc y mtmalloc
- FreeBSD 8 default malloc (jemalloc)
- ¿Acaso malloc?
Actualizar
Si tengo una aplicación cuyo consumo de memoria puede ser muy diferente durante el día y la noche (por ejemplo), ¿puedo forzar a cualquiera de los malloc a devolver la memoria liberada al sistema?
Sin dicho retorno, la memoria liberada se intercambiará y en muchas ocasiones, pero dicha memoria solo contiene basura.
De los que enumera, solo Hoard devolverá la memoria al sistema ... pero si puede hacerlo dependerá mucho del comportamiento de asignación de su programa.
El siguiente análisis se aplica solo a glibc (basado en el algoritmo ptmalloc2). Hay ciertas opciones que parecen útiles para devolver la memoria liberada al sistema:
mallopt() (definido en
malloc.h
) proporciona una opción para establecer el valor de umbral de recorte utilizando una de las opciones de parámetroM_TRIM_THRESHOLD
, esto indica la cantidad mínima de memoria libre (en bytes) permitida en la parte superior del segmento de datos. Si la cantidad cae por debajo de este umbral, glibc invoca abrk()
para devolver memoria al kernel.El valor predeterminado de
M_TRIM_THRESHOLD
en Linux está configurado en 128K, establecer un valor más pequeño podría ahorrar espacio.Se puede lograr el mismo comportamiento estableciendo el valor umbral de recorte en la variable de entorno
MALLOC_TRIM_THRESHOLD_
, sin cambios en la fuente absolutamente.Sin embargo, los programas de prueba preliminares ejecutados utilizando
M_TRIM_THRESHOLD
han demostrado que, aunque la memoria asignada por malloc sí regresa al sistema, la porción restante de la porción real de la memoria (la arena) solicitada inicialmente a través debrk()
tiende a retenerse.Es posible recortar la arena de memoria y devolver cualquier memoria no utilizada al sistema llamando a
malloc_trim(pad)
(definido enmalloc.h
). Esta función cambia el tamaño del segmento de datos, dejando al menos bytes depad
al final y fallando si se puede liberar menos de una página de bytes. El tamaño del segmento siempre es un múltiplo de una página, que es 4.096 bytes en i386.La implementación de este comportamiento modificado de
free()
usandomalloc_trim
podría hacerse usando la funcionalidad malloc hook. Esto no requeriría ningún cambio de código fuente a la biblioteca glibc central.utilizando la llamada al sistema
madvise()
dentro de la implementación gratuita de glibc.
Estoy lidiando con el mismo problema que el OP. Hasta ahora, parece posible con tcmalloc. Encontré dos soluciones:
compile su programa con tcmalloc linked, luego ejecútelo como:
env TCMALLOC_RELEASE=100 ./my_pthread_soft
la documentación menciona que
Las tasas razonables están en el rango [0,10].
pero 10 no me parece suficiente (es decir, no veo ningún cambio).
encuentre algún lugar en su código donde sería interesante liberar toda la memoria liberada, y luego agregue este código:
#include "google/malloc_extension_c.h" // C include #include "google/malloc_extension.h" // C++ include /* ... */ MallocExtension_ReleaseFreeMemory();
La segunda solución ha sido muy efectiva en mi caso; el primero sería genial, pero no es muy exitoso, es complicado encontrar el número correcto, por ejemplo.
La mayoría de las implementaciones no se molestan en identificar aquellos casos (relativamente raros) en los que se han liberado "bloques" completos (de cualquier tamaño que se adapte al sistema operativo) y podrían devolverse, pero, por supuesto, existen excepciones. Por ejemplo, y cito desde la página de wikipedia , en OpenBSD:
En una llamada a
free
, la memoria se libera y no se asigna desde el espacio de direcciones del proceso usando munmap. Este sistema está diseñado para mejorar la seguridad aprovechando la aleatorización de diseño de espacio de direcciones y las funciones de brecha implementadas como parte de la llamada al sistemammap
de OpenBSD, y para detectar fallas de uso después de errores, ya que una asignación de memoria grande no se asigna después de que es liberado, el uso posterior provoca un error de segmentación y la finalización del programa.
La mayoría de los sistemas no están tan centrados en la seguridad como OpenBSD.
Sabiendo esto, cuando estoy codificando un sistema de larga ejecución que tiene un requisito conocido para ser transitorio para una gran cantidad de memoria, siempre trato de fork
el proceso: el padre simplemente espera los resultados del niño [ [normalmente en una tubería]], el niño realiza el cálculo (incluida la asignación de memoria), devuelve los resultados [[en dicha tubería]], luego finaliza. De esta manera, mi proceso de larga duración no será inútil para acaparar la memoria durante los largos tiempos entre ocasionales "picos" en su demanda de memoria. Otras estrategias alternativas incluyen cambiar a un asignador de memoria personalizado para tales requisitos especiales (C ++ lo hace razonablemente fácil, aunque los lenguajes con máquinas virtuales debajo, como Java y Python, por lo general no lo son).
La respuesta corta: para forzar al subsistema malloc a devolver la memoria al sistema operativo, use malloc_trim (). De lo contrario, el comportamiento de la devolución de memoria depende de la implementación.
Para todos los mallocs ''normales'', incluidos los que ha mencionado, la memoria se libera para ser reutilizada por su proceso, pero no de vuelta a todo el sistema. La liberación de todo el sistema solo ocurre cuando finaliza el proceso.
Tuve un problema similar en mi aplicación, después de algunas investigaciones noté que, por algún motivo, glibc no devuelve la memoria al sistema cuando los objetos asignados son pequeños (en mi caso, menos de 120 bytes).
Mira este código:
#include <list>
#include <malloc.h>
template<size_t s> class x{char x[s];};
int main(int argc,char** argv){
typedef x<100> X;
std::list<X> lx;
for(size_t i = 0; i < 500000;++i){
lx.push_back(X());
}
lx.clear();
malloc_stats();
return 0;
}
Salida del programa:
Arena 0:
system bytes = 64069632
in use bytes = 0
Total (incl. mmap):
system bytes = 64069632
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
alrededor de 64 MB no regresan al sistema. Cuando cambié typedef a: typedef x<110> X;
la salida del programa se ve así:
Arena 0:
system bytes = 135168
in use bytes = 0
Total (incl. mmap):
system bytes = 135168
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0
casi toda la memoria fue liberada. También noté que usar malloc_trim(0)
en cualquier caso liberaba memoria al sistema.
Aquí está la salida después de agregar malloc_trim
al código de arriba:
Arena 0:
system bytes = 4096
in use bytes = 0
Total (incl. mmap):
system bytes = 4096
in use bytes = 0
max mmap regions = 0
max mmap bytes = 0