Por qué importa jemalloc: asignación de memoria a escala
jemalloc gestiona la memoria de algunos de los sistemas más grandes del mundo. Cómo reduce la fragmentación y por qué Meta lo sigue apostando.

Cada vez que tu programa llama a malloc(), algo tiene que decidir qué fragmento de memoria virtual devolver. Esta decisión, trivial en un programa pequeño, se convierte en un problema de ingeniería enorme a escala. Un mal asignador fragmenta la memoria, desperdicia RAM, genera contención de bloqueos entre hilos y provoca picos de latencia cuando el SO tiene que recuperar páginas. Un buen asignador no hace nada de eso. jemalloc es un buen asignador, y Meta acaba de renovar su compromiso con él porque, a su escala, la diferencia entre un asignador bueno y uno mediocre son miles de millones de dólares en costes de hardware.
La mayoría de los desarrolladores nunca piensan en la asignación de memoria. Llaman a new o malloc y reciben un puntero. Pero a la escala de Meta, con miles de millones de peticiones diarias, millones de servidores y petabytes de RAM, el comportamiento del asignador tiene un impacto directo y medible en la eficiencia del hardware, la latencia de cola y el coste operativo.
Qué hace realmente un asignador de memoria
El asignador de memoria se sitúa entre tu programa y el sistema operativo. El SO proporciona memoria en bloques grandes (páginas, normalmente de 4 KB o 2 MB). Tu programa necesita memoria en piezas pequeñas de tamaño variable (una cadena de 24 bytes aquí, un buffer de 4096 bytes allá). El trabajo del asignador es trocear las páginas que da el SO en los fragmentos que necesita el programa y reciclar los fragmentos liberados para asignaciones futuras.
El enfoque ingenuo, pedir una página nueva al SO en cada asignación y devolverla al liberar, es catastróficamente lento. Las llamadas al sistema tienen overhead. Las páginas son mucho más grandes que la mayoría de las asignaciones. Usarías 4 KB de memoria para una cadena de 24 bytes.
Los asignadores reales mantienen un pool de memoria y sub-asignan desde él. Los retos son: minimizar la fragmentación (huecos entre asignaciones demasiado pequeños para usarse), minimizar la contención de bloqueos (varios hilos asignando a la vez) y minimizar el overhead (los metadatos por asignación deben ser pequeños en relación con la asignación en sí).
Cómo funciona jemalloc
jemalloc (creado por Jason Evans, de ahí la 'je') se desarrolló originalmente para FreeBSD y más tarde lo adoptó Meta (entonces Facebook) como asignador por defecto en sus servicios de C y C++. Su diseño aborda los tres grandes retos con técnicas concretas.
Las cachés por hilo eliminan la contención. Cada hilo tiene su propia caché de asignaciones pequeñas. Cuando un hilo llama a malloc() para un objeto pequeño, la asignación se sirve por completo desde la caché local del hilo: sin bloqueos, sin operaciones atómicas, sin contención. Solo cuando la caché del hilo se agota acude a la arena compartida para rellenarla.
jemalloc allocation flow:
Thread calls malloc(64)
↓
Check thread cache for 64-byte size class
→ Hit: return cached chunk (no lock, no contention)
→ Miss: refill from arena
↓
Arena (shared, but per-thread affinity)
→ Find a partially-full slab for 64-byte objects
→ Carve out a chunk
→ Return to thread cache
↓
Return chunk to caller
Thread caches handle 95%+ of allocations without any locking.
Las clases de tamaño reducen la fragmentación. En lugar de asignar exactamente el número de bytes pedido, jemalloc redondea a la clase de tamaño más cercana. Las clases están cuidadosamente elegidas: 8, 16, 32, 48, 64, 80, 96, 112, 128, 160, 192, 224, 256, y así sucesivamente, con un espaciado que crece al aumentar el tamaño. Esto significa que una asignación de 50 bytes recibe un fragmento de 64 (un 23 % de desperdicio), lo que suena mal pero en realidad es bueno: como todos los fragmentos de 64 bytes son intercambiables, no hay fragmentación externa dentro de una clase de tamaño.
Los slabs organizan las asignaciones del mismo tamaño. Cada slab es una región contigua de memoria dividida en fragmentos de la misma clase de tamaño. Un slab para objetos de 64 bytes contiene solo fragmentos de 64 bytes. Esto elimina el peor tipo de fragmentación: la memoria liberada que no se puede reutilizar porque queda encajada entre asignaciones activas de tamaños distintos.
El problema de la fragmentación
La fragmentación de memoria es el asesino silencioso de los servicios de larga duración. Un servidor recién arrancado usa la memoria de forma eficiente: las asignaciones están muy compactas. Tras días o semanas de asignaciones y liberaciones mezcladas, el heap se convierte en un queso suizo, con muchos huecos libres pequeños entre asignaciones activas. La memoria libre total podría ser de 2 GB, pero la región contigua libre más grande es de 64 KB.
La fragmentación externa (huecos inutilizables entre asignaciones) y la interna (espacio desperdiciado dentro de las asignaciones por el redondeo) importan, pero la externa es peor. La interna está acotada por el espaciado de las clases de tamaño: como mucho, desperdicias un ~25 % por asignación. La externa no tiene límite y crece con el tiempo.
La estrategia basada en slabs de jemalloc elimina en gran medida la fragmentación externa en las asignaciones pequeñas, que son la inmensa mayoría. Como todos los objetos de un slab tienen el mismo tamaño, al liberar uno queda un hueco exactamente del tamaño adecuado para la siguiente asignación de esa clase. No hay huecos inutilizables.
Para las asignaciones grandes (normalmente por encima de 14 KB), jemalloc usa otra estrategia: cada asignación grande recibe sus propias páginas, y las páginas liberadas pueden devolverse al SO o reutilizarse para otras clases de tamaño. Aquí la fragmentación todavía puede aparecer, pero las asignaciones grandes son relativamente raras.
jemalloc frente a glibc malloc frente a tcmalloc
Los tres asignadores principales del ecosistema Linux hacen compromisos distintos.
- glibc malloc (ptmalloc2) es el predeterminado en la mayoría de sistemas Linux. Usa arenas para escalar con hilos, pero tiene menos clases de tamaño que jemalloc, lo que provoca más fragmentación en servicios de larga duración. Su principal ventaja es ser el predeterminado: no necesita configuración.
- tcmalloc (Google) es un malloc con caché por hilo, desarrollado originalmente para los servicios de C++ de Google. Tiene una excelente caché local por hilo y bajo overhead. Encaja bien con cargas de trabajo con muchas asignaciones de vida corta y encaja peor con cargas de mucha presión de memoria, donde la fragmentación importa.
- jemalloc optimiza para baja fragmentación y comportamiento predecible bajo carga sostenida. Usa clases de tamaño y gestión de slabs más sofisticadas que los otros. El precio: un overhead por asignación algo mayor (más metadatos) a cambio de mejor eficiencia de memoria a largo plazo.
La elección correcta depende de tu carga de trabajo. Para procesos de vida corta, los tres rinden de forma similar. Para servidores de larga duración con patrones de asignación mixtos (servidores web, bases de datos, cachés), la resistencia a la fragmentación de jemalloc suele imponerse: usas entre un 10 % y un 30 % menos de RAM para la misma carga que con glibc malloc, y a escala eso se traduce directamente en ahorro de hardware.
Por qué le importa a Meta
A la escala de Meta, una reducción del 10 % en el uso de memoria de sus servicios de C++ ahorra millones de dólares en hardware. Y no al año, sino al mes. Cuando operas millones de servidores, cada uno ejecutando servicios que asignan y liberan memoria miles de millones de veces al día, la eficiencia del asignador es una partida del presupuesto.
La inversión renovada de Meta en jemalloc se centra en varias áreas: mejor soporte de huge pages (reduciendo los fallos de TLB en sistemas con mucha memoria), mejor devolución de memoria al SO (reduciendo la memoria residente cuando baja la carga) y mejores herramientas de profiling (entender dónde se usa la memoria y dónde se desperdicia).
El aspecto del profiling es especialmente interesante. jemalloc incluye profiling de heap integrado: puedes pedirle que muestree asignaciones y genere un perfil que muestre dónde se asignó la memoria, cuánta está activa frente a liberada y cuán fragmentado está el heap. Este profiling tiene un overhead casi nulo en producción, lo que hace viable ejecutarlo de forma continua en servidores productivos.
Usar jemalloc en tus proyectos
Cambiar a jemalloc suele ser trivial en programas C/C++. En Linux, puedes enlazar directamente contra él o usar LD_PRELOAD para inyectarlo en tiempo de ejecución, sin cambios de código.
# Install jemalloc
sudo apt install libjemalloc-dev # Debian/Ubuntu
brew install jemalloc # macOS
# Use LD_PRELOAD — works with any program, no recompilation
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so ./my_server
# Or link at compile time
gcc -o my_server my_server.c -ljemalloc
# Enable profiling
export MALLOC_CONF="prof:true,prof_prefix:jeprof"
./my_server
# Then analyze: jeprof --svg ./my_server jeprof.*.heap > heap.svg
Varios proyectos de perfil alto usan jemalloc por defecto: Redis, Rust (usa jemalloc como asignador por defecto en algunas plataformas), Firefox (jemalloc se desarrolló originalmente para FreeBSD, la plataforma a la que apuntaba Firefox) y muchos motores de juegos.
Para lenguajes con memoria gestionada (Python, Java, Go), el runtime del lenguaje se encarga de la asignación y jemalloc no es directamente aplicable. Pero los conceptos (caché local por hilo, clases de tamaño, asignación por slabs) aparecen en todos los runtimes modernos y en sus recolectores de basura. El asignador de runtime de Go, por ejemplo, usa un diseño inspirado en tcmalloc con cachés por P (procesador) y clases de tamaño.
La infraestructura invisible
La asignación de memoria es infraestructura invisible cuando funciona y catastrófica cuando no. Un servidor que pierde memoria poco a poco por fragmentación acabará con un OOM-kill, y la causa no aparecerá en ningún log de aplicación: está por debajo de la capa de la aplicación.
Para la mayoría de aplicaciones, el asignador por defecto es suficiente. Pero si ejecutas servidores de larga vida, sufres un crecimiento de memoria inexplicable o operas a una escala donde la eficiencia del hardware importa, entender tu asignador (y quizá cambiarlo por uno mejor) es uno de los cambios de infraestructura con mayor palanca que puedes hacer. jemalloc no es magia. Es ingeniería: diseño cuidadoso de estructuras de datos, compromisos bien informados y una atención implacable a los detalles que importan a escala.


