Heap - Gestor de memoria
Gestor de memoria dinámica optimizado y control de leaks.
Funciones
void | heap_start_mt (void) |
void | heap_end_mt (void) |
void | heap_verbose (...) |
void | heap_stats (...) |
bool_t | heap_leaks (void) |
byte_t* | heap_malloc (...) |
byte_t* | heap_calloc (...) |
byte_t* | heap_realloc (...) |
byte_t* | heap_aligned_malloc (...) |
byte_t* | heap_aligned_calloc (...) |
byte_t* | heap_aligned_realloc (...) |
void | heap_free (...) |
type* | heap_new (...) |
type* | heap_new0 (...) |
type* | heap_new_n (...) |
type* | heap_new_n0 (...) |
type* | heap_realloc_n (...) |
void | heap_delete (...) |
void | heap_delete_n (...) |
void | heap_auditor_add (...) |
void | heap_auditor_delete (...) |
Heap es gestor y auditor de memoria dinámica muy eficiente incluido en la librería core y disponible para todos los proyectos basados en NAppGUI (librerías y aplicaciones). Es habitual que las aplicaciones soliciten gran cantidad de pequeños bloques de memoria para albergar diferentes objetos (cadenas de caracteres, controles de interfaz, instancias de estructuras, búfer E/S, etc). La estrategia detrás de gestor no es otra que pedir al sistema operativo páginas de memoria de cierto tamaño (64kb o más) mediante bmem_malloc y utilizarlas para resolver varias solicitudes de manera muy eficiente.
- Utiliza heap_new para crear dinámicamente un objeto.
- Utiliza heap_malloc para reservar un bloque de memoria.
- Utiliza heap_delete para destruir un objeto.
- Utiliza heap_free para liberar un bloque de memoria.
1 2 3 4 5 6 7 8 |
Product *product = heap_new(Product); byte_t *memblock = heap_malloc(1024, "MyOwnBlock"); // Do something ... heap_delete(&product, Product); heap_free(&memblock, "MyOwnBlock"); |
El uso de Heap en lugar de llamadas al sistema nos proporcionará ciertos beneficios:
- Rendimiento: Una llamada a heap_malloc se resuelve tan solo incrementando el valor de un contador. heap_free únicamente actualiza la cabecera de la página afectada.
- Localidad: Dos llamadas consecutivas a
heap_malloc()
son ubicadas en posiciones físicas de memoria contiguas. Esto reduce el número de fallos de caché ya que, según el principio de localidad, hay una gran probabilidad de que dos objetos que se crean juntos se utilicen juntos. - Memory leaks: heap apunta las reservas y liberaciones por tipo de objeto. Llegado el caso, avisará al programador por medio de Asserts o Log de que existen objetos no liberados. La gran ventaja de este auditor sobre otras herramientas es que siempre se está ejecutando como parte del programa. Esto explota la coherencia temporal, ya que si tras un cambio se detectan leaks donde antes no había, es muy probable que podamos acotar y detectar el error, ya que será algo en lo que acabamos de trabajar.
- Estadísticas: Podemos obtener perfiles del uso de memoria (tiempo/bytes). Esto puede ayudarnos a detectar cuellos de botella (especialmente en el arranque) u optimizar el tamaño de página.
1. Memoria múlti-hilo
Por defecto, heap está configurado para funcionar de forma óptima en aplicaciones mono-hilo. Si queremos que varias hebras del mismo proceso reserven o liberen memoria dinámica de forma concurrente y segura deberemos utilizar:
- heap_start_mt para iniciar el soporte multi-hilo.
- heap_end_mt para terminar el soporte multi-hilo.
En el momento que se llama a heap_start_mt, se activan los mecanismos de sincronización dentro del Heap para garantizar la exclusión mutua al gestor de memoria hasta que se reciba una llamada a heap_end_mt que lo devolverá al modo de funcionamiento mono-hilo. Llamadas sucesivas a heap_start_mt
se irán acumulando, por lo que permanecerá en modo multi-hilo hasta que se cierren todos los bloques abiertos (Listado 1). Es responsabilidad del programador utilizar este par de funciones en aquellos puntos del programa que lo requieran.
Toda sección que comienza conheap_start_mt
debe cerrarse conheap_end_mt
.
No hay ningún problema en activar el soporte multi-hilo en secciones mono-hilo, salvo una ligera penalización en el rendimiento.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Single-threaded block ... ... heap_start_mt(); // Multi-threaded block ... heap_start_mt(); ... heap_end_mt(); // Continue multi-threaded block ... heap_end_mt(); // Single-threaded block ... |
2. Como funciona Heap
Al iniciar el programa, heap crea una página de memoria por defecto. Los primeros bytes se reservan como cabecera, una pequeña estructura que controla el estado de la página. Cada petición se asigna secuencialmente dentro de la misma página, incrementando el valor de un puntero (Figura 1). Cuando la página se queda sin espacio, se crea una nueva mediante bmem_malloc, que es enlazada con la anterior y etiquetada como la nueva página por defecto (Figura 2). Cada llamada a heap_free actualiza la cabecera con el número de bloques/bytes liberados (Figura 3). Estos bloques no son reutilizados, de lo contrario se complicaría la lógica de heap volviéndolo más lento. La dirección de la cabecera es guardada al final de cada bloque, por lo que no hay que iterar para localizarlo. Cuando todos los bloques de la página han sido liberados, la página entera es destruida por bmem_free y los punteros entre páginas vecinas restaurados (Figura 4).
Heap también contabiliza el número de reversas/liberaciones por tipo de objeto mediante el parámetro name
de heap_malloc. Al finalizar la ejecución del programa, si la aplicación carece de memory leaks, escribirá en Log un mensaje como este:
1 2 3 4 5 6 7 8 |
[12:58:08] [OK] Heap Memory Staticstics [12:58:08] ============================ [12:58:08] Total a/dellocations: 1126, 1126 [12:58:08] Total bytes a/dellocated: 74611, 74611 [12:58:08] Max bytes allocated: 54939 [12:58:08] Effective reallocations: (0/34) [12:58:08] Real allocations: 2 pages of 65536 bytes [12:58:08] ============================ |
Pero si tras la ejecución, la aplicación tiene memoria por liberar, el mensaje será distinto:
1 2 3 4 5 6 7 8 9 10 11 |
[13:00:35] [FAIL] Heap Object Leaks!!! [13:00:35] =========================== [13:00:35] 'App' a/deallocations: 1, 0 (1 leaks) [13:00:35] 'String' a/deallocations: 414, 410 (4 leaks) [13:00:35] =========================== [13:00:35] [FAIL] Heap Global Memory Leaks!!! [13:00:35] ================================== [13:00:35] Total a/dellocations: 1161, 1156 (5 leaks) [13:00:35] Total bytes a/dellocated: 75704, 75596 (108 bytes) [13:00:35] Max bytes allocated: 54939 [13:00:35] ================================== |
Que advierte que tenemos un objeto App
y cuatro String
sin liberar. Si en la ejecución anterior no había leaks, es muy probable que podamos acotar el error sin demasiada dificultad.
El auditor de heap no pretende sustituir a herramientas más avanzadas de testeo de memoria, tan solo es un primer filtro que nos avisa constantemente durante la fase de desarrollo y test. A pesar de que la sobrecarga que produce en tiempo de ejecución es mínima, el auditor se deshabilita complemente en la configuración Release
.
heap_start_mt ()
Inicia una sección multi-hilo.
void
heap_start_mt(void);
Observaciones
Ver Memoria múlti-hilo.
heap_end_mt ()
Finaliza una sección multi-hilo.
void
heap_end_mt(void);
Observaciones
Ver Memoria múlti-hilo.
heap_verbose ()
Activa/desactiva del modo 'verbose' del auditor de memoria.
void heap_verbose(bool_t verbose);
verbose |
|
Observaciones
Por defecto FALSE
.
heap_stats ()
Activa/desactiva las estadísticas del auditor de memoria.
void heap_stats(bool_t stats);
stats |
|
Observaciones
Por defecto TRUE
.
heap_leaks ()
Retorna TRUE
si existen fugas de memoria al acabar la ejecución.
bool_t heap_leaks(void);
Retorna
TRUE
si existen fugas.
heap_malloc ()
Reserva un bloque de memoria con la alineación por defecto sizeof(void*)
.
byte_t* heap_malloc(const uint32_t size, const char_t *name);
1 2 3 |
byte_t *mem = heap_malloc(1024 * 768, "PixelBuffer"); ... heap_free(&mem, 1024 * 768, "PixelBuffer"); |
size | Tamaño en bytes del bloque. |
name | Texto de referencia para el auditor. |
Retorna
Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.
Observaciones
Usa esta función para bloques genéricos. Para tipos utiliza heap_new.
heap_calloc ()
Igual que heap_malloc, pero inicializando el bloque con 0s.
byte_t* heap_calloc(const uint32_t size, const char_t *name);
1 2 3 4 |
byte_t *mem = heap_calloc(256 * 256, "DrawCanvas"); /* mem = {0, 0, 0, 0, ..., 0}; */ ... heap_free(&mem, 256 * 256, "DrawCanvas"); |
size | Tamaño en bytes del bloque. |
name | Texto de referencia para el auditor. |
Retorna
Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.
Observaciones
Usa esta función para bloques genéricos. Para tipos utiliza heap_new.
heap_realloc ()
Realoja un bloque de memoria existente debido a la expansión o reducción del mismo. Garantiza que se conserva el contenido previo del bloque min(size, new_size)
. Intenta hacerlo sin mover memoria (in situ), pero si no es posible busca una nueva zona. También garantiza la alineación por defecto sizeof(void*)
si hay que reservar un nuevo bloque.
byte_t* heap_realloc(byte_t *mem, const uint32_t size, const uint32_t new_size, const char_t *name);
1 2 3 4 5 |
byte_t *mem = heap_malloc(64, "ArrayData"); ... mem = heap_realloc(mem, 64, 128, ArrayData); ... heap_free(&mem, 128, "ArrayData"); |
mem | Puntero al bloque original a realojar. |
size | Tamaño en bytes del bloque original |
new_size | Nuevo tamaño requerido, en bytes. |
name | Texto de referencia para el auditor. Debe ser el mismo que el utilizado en heap_malloc. |
Retorna
Puntero al bloque realojado. Será el mismo que el puntero original mem
si la reubicación "in-situ" ha tenido éxito. Debe ser liberado con heap_free cuando ya no sea necesario.
Observaciones
Usa esta función para bloques genéricos. Para tipos utiliza heap_realloc_n.
heap_aligned_malloc ()
Reserva un bloque de memoria con alineación.
byte_t* heap_aligned_malloc(const uint32_t size, const uint32_t align, const char_t *name);
1 2 3 |
byte_t *sse_data = heap_aligned_malloc(256 * 16, 16, "Vectors"); ... heap_free(&mem, 256 * 16, "Vectors"); |
size | Tamaño en bytes del bloque. |
align | Alineación. Debe ser potencia de 2. |
name | Texto de referencia para el auditor. |
Retorna
Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.
heap_aligned_calloc ()
Igual que heap_aligned_malloc, pero inicializando el bloque con 0s.
byte_t* heap_aligned_calloc(const uint32_t size, const uint32_t align, const char_t *name);
1 2 3 4 |
byte_t *sse_data = heap_aligned_calloc(256 * 16, 16, "Vectors"); /* see_data = {0, 0, 0, 0, ..., 0}; */ ... heap_free(&mem, 256 * 16, "Vectors"); |
size | Tamaño en bytes del bloque. |
align | Alineación. Debe ser potencia de 2. |
name | Texto de referencia para el auditor. |
Retorna
Puntero al nuevo bloque. Debe ser liberado con heap_free cuando ya no sea necesario.
heap_aligned_realloc ()
Igual que heap_realloc, pero garantizando la alineación de memoria.
byte_t* heap_aligned_realloc(byte_t *mem, const uint32_t size, const uint32_t new_size, const uint32_t align, const char_t *name);
1 2 3 4 5 |
byte_t *sse_data = heap_aligned_malloc(256 * 16, 16, "Vectors"); ... sse_data = heap_aligned_realloc(sse_data, 256 * 16, 512 * 16, 16, "Vectors"); ... heap_free(&mem, 512 * 16, "Vectors"); |
mem | Puntero al bloque original a realojar. |
size | Tamaño en bytes del bloque original |
new_size | Nuevo tamaño requerido, en bytes. |
align | Alineación. Debe ser potencia de 2. |
name | Texto de referencia para el auditor. Debe ser el mismo que el utilizado en heap_aligned_malloc. |
Retorna
Puntero al bloque realojado. Debe ser liberado con heap_free cuando ya no sea necesario.
heap_free ()
Libera la memoria apuntada por mem
, previamente reservada por heap_malloc, heap_realloc o sus equivalentes con alineación.
void heap_free(byte_t **mem, const uint32_t size, const char_t *name);
mem | Doble puntero al bloque a liberar. Será puesto a |
size | Tamaño del bloque de memoria. |
name | Texto de referencia para el auditor, debe ser el mismo que el utilizado en heap_malloc. |
Observaciones
Usa esta función para bloques de memoria genéricos. Para tipos utiliza heap_delete.
heap_new ()
Reserva memoria para un objeto. El puntero de retorno es convertido a type
.
type* heap_new(type);
1 2 3 |
MyAppCtrl *ctrl = heap_new(MyAppCtrl); ... heap_delete(&ctrl, MyAppCtrl); |
type | Tipo de objeto. |
Retorna
Puntero al objeto creado. Debe ser destruido por heap_delete cuando ya no sea necesario.
heap_new0 ()
Igual que heap_new, pero inicializando el objeto con 0s.
type* heap_new0(type);
1 2 3 4 |
MyAppModel *model = heap_new0(MyAppModel); /* model = {0} */ ... heap_delete(&model, MyAppModel); |
type | Tipo de objeto. |
Retorna
Puntero al objeto creado. Debe ser destruido por heap_delete cuando ya no sea necesario.
heap_new_n ()
Reserva memoria para n
objetos. El puntero de retorno es convertido a type
.
type* heap_new_n(const uint32_t n, type);
1 2 3 |
Car *cars = heap_new_n(10, Car); ... heap_delete_n(&cars, 10, Car); |
n | Número de objetos a crear. |
type | Tipo de objeto. |
Retorna
Puntero al array recién creado. Debe ser destruido por heap_delete_n cuando ya no sea necesario.
heap_new_n0 ()
Igual que heap_new_n, pero inicializando el array con 0s.
type* heap_new_n0(const uint32_t n, type);
1 2 3 4 |
Car *cars = heap_new_n0(10, Car); /* cars = {0, 0, 0, ..., 0}; */ ... heap_delete_n(&cars, 10, Car); |
n | Número de objetos a crear. |
type | Tipo de objeto. |
Retorna
Puntero al array recién creado. Debe ser destruido por heap_delete_n cuando ya no sea necesario.
heap_realloc_n ()
Realoja un array de objetos creados dinámicamente con heap_new_n o heap_new_n0. Garantiza que los objetos previos permanezcan inalterados min(size, new_size)
.
type* heap_realloc_n(type *mem, const uint32_t size, const uint32_t new_size, type);
1 2 3 4 5 6 |
Car *cars = heap_new_n(10, Car); ... cars = heap_realloc_n(cars, 10, 20, Car); /* cars[0]-[9] remains untouched. */ ... heap_delete_n(&cars, 20, Car); |
mem | Puntero al array a realojar. |
size | Número de elementos del array original |
new_size | Nuevo tamaño requerido (en elementos). |
type | Tipo de objeto. |
Retorna
Puntero al array realojado. Debe ser destruido por heap_delete_n cuando ya no sea necesario.
heap_delete ()
Libera el objeto apuntado por obj
, previamente reservado por heap_new o heap_new0.
void heap_delete(type **obj, type);
obj | Doble puntero al objeto a liberar. Será puesto a |
type | Tipo de objeto. |
heap_delete_n ()
Libera n
objetos apuntados por obj
, previamente reservado por heap_new_n, heap_new_n0.
void heap_delete_n(type **obj, const uint32_t n, type);
obj | Doble puntero al array a liberar. Será puesto a |
n | Número de objetos a liberar, el mismo que en la reserva. |
type | Tipo de objeto. |
heap_auditor_add ()
Añade un objeto opaco al auditor de memoria.
void heap_auditor_add(const char_t *name);
name | Nombre del objeto a añadir. |
heap_auditor_delete ()
Libera un objeto opaco del auditor de memoria.
void heap_auditor_delete(const char_t *name);
name | Nombre del objeto a liberar. |