Tengo una aplicación de compresión de imágenes que ahora tiene dos versiones diferentes de sistemas de asignación de memoria. En el original, malloc se usa en todas partes, y en el segundo, implementé un asignador de grupo simple, que solo asigna una parte de la memoria y devuelve partes de esa memoria a las llamadas myalloc().
Hemos notado una gran sobrecarga de memoria cuando se usa malloc: en el punto máximo de su uso de memoria, el código malloc() requiere alrededor de 170 megabytes de memoria para una imagen de 1920x1080x16bpp, mientras que el asignador de grupos asigna solo 48 megabytes, de los cuales 47 son utilizados por el programa.
En términos de patrones de asignación de memoria, el programa asigna muchos bloques de 8 bytes (la mayoría), 32 bytes (muchos) y 1080 bytes (algunos) con la imagen de prueba. Aparte de estos, no hay asignaciones de memoria dinámica en el código.
El sistema operativo del sistema de prueba es Windows 7 (64 bits).
¿Cómo probamos el uso de la memoria?
Con el asignador personalizado, pudimos ver cuánta memoria se usa porque todas las llamadas malloc se transfieren al asignador. Con malloc(), en el modo de depuración simplemente revisamos el código y observamos el uso de la memoria en el administrador de tareas. En el modo de lanzamiento hicimos lo mismo, pero menos detallado porque el compilador optimiza muchas cosas para que no pudiéramos revisar el código pieza por pieza (la diferencia de memoria entre el lanzamiento y la depuración era de unos 20 MB, lo que atribuiría a optimización y falta de información de depuración en el modo de lanzamiento).
¿Podría malloc por sí solo ser la causa de una sobrecarga tan grande? Si es así, ¿qué causa exactamente esta sobrecarga dentro de malloc?
En Windows 7, tendrá siempre obtenga el asignador de almacenamiento dinámico de baja fragmentación, sin llamar explícitamente a HeapSetInformation() para solicitarlo. Ese asignador sacrifica espacio de memoria virtual para reducir la fragmentación. Su programa en realidad no está usando 170 megabytes, solo está viendo un montón de bloques libres esperando una asignación de un tamaño similar.
Este algoritmo es muy fácil de superar con un asignador personalizado que no hace nada para reducir la fragmentación. Lo que bien puede funcionar para usted, aunque no verá los efectos secundarios hasta que mantenga el programa funcionando por más tiempo que una sola sesión de depuración. Debe asegurarse de que sea estable durante días o semanas si ese es el patrón de uso esperado.
Lo mejor que puedes hacer es simplemente no preocuparte por eso, 170 MB son papas bastante pequeñas. Y tenga en cuenta que esto es virtual memoria, no cuesta nada.
En primer lugar, malloc alinea los punteros con límites de 16 bytes. Además, almacenan al menos un puntero (o longitud asignada) en las direcciones que preceden al valor devuelto. Luego, probablemente agreguen un valor mágico o un contador de liberación para indicar que la lista enlazada no está rota o que el bloque de memoria no se ha liberado dos veces (ASSERTS libres para liberaciones dobles).
#include <stdlib.h>
#include <stdio.h>
int main(int ac, char**av)
{
int *foo = malloc(4);
int *bar = malloc(4);
printf("%d\n", (int)bar - (int)foo);
}
Vuelta: 32
Precaución: cuando ejecuta su programa en Visual Studio o con cualquier depurador adjunto, el comportamiento de malloc cambia mucho de forma predeterminada. No se utiliza el montón de fragmentación baja y una sobrecarga de memoria puede no ser representativa del uso real (ver también https://stackoverflow.com/a/3768820/16673). Debe usar la variable de entorno _NO_DEBUG_HEAP=1 para evitar que esto le afecte o para medir el uso de la memoria cuando no se ejecuta con un depurador.
Normalmente, solo escribe una versión personalizada de algo genérico cuando lo que escribe agregará muchos beneficios en el rendimiento debido a que conoce los detalles de cómo planea usarlo. Sin embargo, no esperaría una sobrecarga de memoria por usar malloc. ¿Estás seguro de que estás midiendo correctamente el uso de la memoria? ¿Estás seguro de que estás liberando la memoria correctamente cuando usas malloc?
– CashCow
25 de octubre de 2012 a las 8:51
Todo en el código malloc se libera (lo probé con un perfilador de memoria), pero solo al final de la aplicación antes de que termine de ejecutarse, por lo que las mediciones ocurren antes de que se llame a cualquier función free() (en ambas versiones). El asignador personalizado acelera todo y nos ahorra unos 15 ms por imagen (ya que es solo una asignación grande en lugar de muchas pequeñas).
– TravisG
25 de octubre de 2012 a las 8:57
Un 200% de gastos generales para
malloc
parece excesivo, a menos que haya muchas más asignaciones de 8 bytes de las que cree.–Steve Jessop
25 de octubre de 2012 a las 8:57
malloc bien puede “retener” algo de memoria después de haberla liberado para su uso inmediato. Si su implementación usa std::vector, puede “reservar” con anticipación, aunque cuando va a asignar cantidades tan grandes de memoria, es mejor no elegir un modelo que requiera un búfer contiguo.
– CashCow
25 oct 2012 a las 9:00
¿Pueden describir cómo midieron el uso de la memoria?
– Um Nyobe
25 de octubre de 2012 a las 9:04