1. Dos costos que permanecen con los datos en RAM
En los temas de memoria virtual estudiamos el costo de traer páginas que no están presentes. Ahora supondremos que el conjunto de trabajo ya está en RAM. Todavía quedan, entre otros, dos problemas:
- Traducir muchas direcciones. Si las traducciones no caben en la TLB, el hardware debe recorrer tablas de páginas con frecuencia.
- Acceder a memoria distante. En una máquina con varios nodos, la RAM conectada al nodo de otra CPU puede requerir un recorrido más costoso.
Una página grande puede ayudar al primer problema sin resolver el segundo. Fijar un hilo a una CPU puede estabilizar su ejecución sin acercar las páginas que otro hilo ya había inicializado. Son decisiones relacionadas, pero distintas.
Usaremos KiB, MiB y GiB con base 1024: 4 KiB son 4096 bytes y 2 MiB son 2 097 152 bytes. Los tamaños que aparecen en los ejemplos son habituales en x86-64; la arquitectura y la configuración del núcleo determinan cuáles están disponibles.
2. Páginas grandes y alcance de la TLB
Página grande es una página de mayor tamaño que la página base del sistema. Cuando el hardware permite traducirla con una sola entrada, esa traducción cubre un intervalo virtual mucho mayor.
Con páginas base de 4 KiB, una región de 2 MiB necesita 512 páginas. Una traducción de 2 MiB puede sustituir esas 512 traducciones base para ese intervalo. En x86-64 pueden existir páginas de 4 KiB, 2 MiB y, si el hardware lo admite, 1 GiB. Una entrada de un nivel superior de la tabla puede actuar como entrada final para una página grande.
Cobertura = E × P
Páginas de una región = techo(tamaño / P)
64 entradas × 4 KiB = 256 KiB
64 entradas × 2 MiB = 128 MiB
64 entradas × 1 GiB = 64 GiB
| Región de 1 GiB, alineada | Páginas necesarias | Traducciones si cada página usa una entrada |
|---|---|---|
| Páginas de 4 KiB | 262 144 | 262 144 |
| Páginas de 2 MiB | 512 | 512 |
| Página de 1 GiB | 1 | 1 |
Estas cuentas muestran el potencial, no describen toda la TLB real. Los procesadores pueden tener varios niveles, conjuntos, asociatividad y capacidades distintas para cada tamaño. No se debe asumir que una máquina tiene exactamente 64 entradas útiles para cualquier página.
También conviene distinguir fallo de TLB de fallo de página. El primero puede resolverse recorriendo tablas sin entrar al núcleo. El segundo requiere que el sistema atienda una condición de la traducción o protección. Reducir fallos de TLB no implica reducir en la misma proporción todas las faltas de caché ni todos los accesos a RAM.
3. Beneficios y costos de aumentar la página
Una carga que recorre grandes arreglos puede beneficiarse de menos traducciones y de tablas de páginas más pequeñas cuando las páginas se mapean desde niveles superiores. En cambio, un programa que utiliza solo unos pocos bytes de cada región grande puede desperdiciar memoria.
| Aspecto | Posible beneficio | Costo o condición |
|---|---|---|
| TLB | Más bytes cubiertos por traducción | Capacidad y comportamiento específicos del hardware |
| Tablas | Menos entradas finales con mapeos grandes | No toda asignación grande usa una única entrada grande |
| Primer acceso | Menos eventos de asignación en ciertos casos | Poner a cero o compactar bloques grandes puede aumentar la latencia |
| Fragmentación | Menos objetos de administración por región densa | Mayor desperdicio con uso escaso o tamaños pequeños |
| Ubicación NUMA | Menos unidades que administrar | Una unidad grande puede mezclar datos de consumidores distintos |
Con una asignación que deba consumir páginas completas de 2 MiB, 5 MiB útiles ocupan 3 páginas, es decir, 6 MiB: queda 1 MiB sin aprovechar. Un mapeo THP puede combinar páginas base y grandes o dividirse, por lo que no se debe aplicar mecánicamente esa cuenta a cualquier región de Linux.
Una página grande requiere un bloque físico contiguo del tamaño correspondiente en los mecanismos que usamos aquí. Que haya suficiente RAM libre en total no garantiza que exista un bloque adecuado. La compactación intenta reunir memoria libre, pero tiene costo y puede no lograrlo.
4. HugeTLB y Transparent Huge Pages
Linux ofrece mecanismos diferentes bajo el nombre informal de «huge pages». Conviene identificarlos antes de interpretar una medición:
| Propiedad | HugeTLB | Transparent Huge Pages, THP |
|---|---|---|
| Solicitud | Explícita: MAP_HUGETLB, hugetlbfs y otras interfaces | El núcleo decide sobre regiones elegibles; madvise permite expresar preferencia |
| Recursos | Pool de páginas grandes administrado por el sistema | Asignación dentro de la gestión ordinaria de memoria |
| Si no hay página grande adecuada | La solicitud puede fallar | Normalmente puede continuar con páginas base |
| Intercambio | Las páginas HugeTLB no se envían a swap | La memoria respaldada mediante THP puede participar en la gestión de swap |
| Control y observación | Pool por tamaño y contadores HugePages | Políticas THP y estadísticas del mapeo |
Las THP tradicionales de tamaño PMD suelen ser de 2 MiB cuando la página base es de 4 KiB en x86-64. Los núcleos modernos también pueden ofrecer mTHP, tamaños intermedios como 16 o 64 KiB según la plataforma. Una asignación física de varias páginas no equivale necesariamente a una única entrada PMD: parte de estas variantes se mapea con entradas PTE.
Por eso «THP está activado» no demuestra que una región concreta use exclusivamente páginas de 2 MiB. Intervienen las políticas por tamaño, la elegibilidad de la región, la disponibilidad de memoria y el momento en que se consulta.
5. Solicitar THP desde C
madvise con MADV_HUGEPAGE marca una región como candidata. Su éxito no garantiza que se hayan asignado páginas grandes. Para comparar usaremos una ejecución nueva con MADV_NOHUGEPAGE, que evita THP en esa región.
El programa crea 64 MiB, aplica la preferencia antes de escribir y toca cada página base. Luego espera Enter para que puedas examinar el proceso desde otra terminal. Cada modo debe ejecutarse por separado.
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 2 || (strcmp(argv[1], "base") != 0 &&
strcmp(argv[1], "thp") != 0)) {
fprintf(stderr, "Uso: %s base|thp\n", argv[0]);
return 1;
}
long n = sysconf(_SC_PAGESIZE);
if (n <= 0) {
fprintf(stderr, "No se pudo consultar la página base\n");
return 1;
}
size_t tam = 64UL * 1024 * 1024;
unsigned char *p = mmap(NULL, tam, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) { perror("mmap"); return 1; }
int consejo = strcmp(argv[1], "thp") == 0 ? MADV_HUGEPAGE : MADV_NOHUGEPAGE;
if (madvise(p, tam, consejo) == -1) {
perror("madvise"); munmap(p, tam); return 1;
}
for (size_t i = 0; i < tam; i += (size_t) n)
p[i] = 1;
printf("PID: %ld; base: %p; tamaño: %zu; modo: %s\n",
(long) getpid(), (void *) p, tam, argv[1]);
puts("Consulta smaps y numa_maps. Pulsa Enter para terminar.");
getchar();
if (munmap(p, tam) == -1) { perror("munmap"); return 1; }
return 0;
}
$ gcc -std=c17 -Wall -Wextra -O2 observar_thp.c -o observar_thp
$ ./observar_thp base
PID: 4217; base: 0x7f...; tamaño: 67108864; modo: base
Consulta smaps y numa_maps. Pulsa Enter para terminar.
$ ./observar_thp thp
PID: 4220; base: 0x7f...; tamaño: 67108864; modo: thp
Consulta smaps y numa_maps. Pulsa Enter para terminar.
La base devuelta por mmap está alineada a página base, pero no se debe asumir que cualquier dirección y longitud cubren solo intervalos completos de página grande. Puede haber extremos con páginas base. El núcleo también puede promover regiones elegibles después de su creación.
Consulta la región por su dirección, no solo el total del proceso. El campo AnonHugePages informa sobre respaldo anónimo grande; THPeligible, cuando aparece, informa elegibilidad, y VmFlags puede reflejar las preferencias. No concluyas que una región carece de THP únicamente porque KernelPageSize o MMUPageSize muestre 4 kB: los campos y la representación de regiones mixtas necesitan interpretarse juntos.
6. Una reserva explícita con MAP_HUGETLB
Para una página HugeTLB de 2 MiB en Linux podemos combinar MAP_HUGETLB con MAP_HUGE_2MB. Esta variante necesita soporte, recursos y autorización adecuados. Si no están disponibles, el programa informa el error y termina; no configura un pool ni modifica la máquina.
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/mman.h>
int main(void)
{
#if defined(MAP_HUGETLB) && defined(MAP_HUGE_2MB)
size_t tam = 2UL * 1024 * 1024;
unsigned char *p = mmap(NULL, tam, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_2MB, -1, 0);
if (p == MAP_FAILED) {
perror("mmap HugeTLB de 2 MiB");
fprintf(stderr, "Comprueba soporte, pool disponible y permisos.\n");
return 1;
}
p[0] = 7;
p[tam - 1] = 9;
printf("HugeTLB: %u y %u\n", (unsigned) p[0], (unsigned) p[tam - 1]);
if (munmap(p, tam) == -1) { perror("munmap"); return 1; }
return 0;
#else
fprintf(stderr, "Las cabeceras no exponen esta variante HugeTLB.\n");
return 1;
#endif
}
$ gcc -std=c17 -Wall -Wextra -O2 hugetlb_2m.c -o hugetlb_2m
$ ./hugetlb_2m
mmap HugeTLB de 2 MiB: Cannot allocate memory
Comprueba soporte, pool disponible y permisos.
Si tiene éxito, la salida será HugeTLB: 7 y 9. No se debe sustituir un error por una reserva base silenciosa y luego afirmar que se midieron páginas HugeTLB. La longitud y los desplazamientos deben respetar las exigencias del tamaño elegido; al desmapear HugeTLB también hay requisitos de alineación a ese tamaño.
El pool reduce la memoria disponible para otros usos. Reservarlo es una decisión de administración y capacidad, no un paso obligatorio para estudiar THP. En esta práctica basta con observar su estado y comprobar el manejo de errores.
7. Qué es una máquina NUMA
NUMA, Non-Uniform Memory Access, es una organización en la que el costo de acceder a memoria depende de la relación entre el procesador que ejecuta el acceso y el nodo donde reside esa memoria.
Un nodo puede agrupar CPU y memoria conectadas con mayor proximidad. Un acceso local se atiende desde la memoria de ese nodo; uno remoto atraviesa una interconexión. Los nodos no equivalen necesariamente uno a uno a sockets: un socket puede exponer varios nodos y también puede haber nodos sin CPU.
| CPU que ejecuta | Página en nodo 0 | Página en nodo 1 |
|---|---|---|
| CPU del nodo 0 | Acceso local | Acceso remoto |
| CPU del nodo 1 | Acceso remoto | Acceso local |
No existe una penalización universal de «el doble». Influyen latencia, ancho de banda, interconexión, carga y cachés. Si el dato ya está en una caché cercana, no se realiza necesariamente una lectura de DRAM. Las distancias que muestra el sistema son indicadores relativos de topología, no una tabla de nanosegundos por acceso.
En una máquina de un solo nodo, una máquina virtual o un contenedor con visibilidad restringida, tal vez no haya dos nodos disponibles para el experimento. Eso no invalida las ideas, pero impide medir allí una diferencia local/remota real.
8. Primer toque y afinidad
En el caso común de memoria anónima con política local predeterminada, el nodo de asignación se decide al materializar la página, no al reservar el intervalo virtual. De ahí la regla práctica del primer toque: el hilo que provoca la asignación inicial influye en dónde se ubica la página.
Para experimentar, conviene usar una primera escritura. Una lectura inicial puede resolverse con una página de ceros compartida sin asignar todavía la página privada que después usará el programa. También hay excepciones a la asignación local por políticas explícitas, presión de memoria y restricciones de nodos.
- Un hilo del nodo 0 reserva un arreglo grande con mmap, sin tocarlo.
- Si ese hilo inicializa todo el arreglo, las páginas privadas tienden a asignarse cerca de ese hilo bajo la política local.
- Después, trabajadores del nodo 1 pueden realizar accesos remotos a esas páginas.
- Si cada trabajador inicializa la parte que luego utilizará, puede mejorar la localidad, siempre que las páginas y las particiones permitan esa distribución.
La afinidad de CPU restringe dónde se ejecuta un hilo. La política de memoria guía dónde se asignan sus páginas. Cambiar afinidad después de inicializar no migra automáticamente todos los datos al nuevo nodo.
Linux puede realizar balanceo NUMA automático y migración de páginas en determinados entornos. Tampoco son instantáneos ni gratuitos. Si se modifica una política, las páginas existentes no cambian necesariamente de ubicación: algunas interfaces permiten pedir migración explícita, con condiciones y límites propios.
9. Políticas de memoria con numactl
numactl permite inspeccionar nodos y ejecutar un programa con restricciones de CPU y políticas de memoria. Empieza por la topología y los recursos permitidos al proceso:
$ numactl --hardware
$ numactl --show
$ lscpu -e=CPU,NODE,SOCKET,CORE
$ grep -E 'Cpus_allowed_list|Mems_allowed_list' /proc/self/status
| Opción | Significado | Precaución al interpretar |
|---|---|---|
--cpunodebind=0 | Ejecutar en CPU del nodo 0 | No coloca por sí misma toda página existente en ese nodo |
--membind=0 | Restringir asignaciones a los nodos indicados | Puede fallar o agotar recursos sin recurrir a otros nodos |
--preferred=0 | Preferir un nodo, permitiendo alternativas | No garantiza que todas las páginas terminen allí |
--interleave=0,1 | Distribuir asignaciones entre nodos | Puede repartir ancho de banda a costa de accesos remotos |
$ numactl --cpunodebind=0 --membind=0 ./observar_thp base
$ numactl --cpunodebind=1 --membind=0 ./observar_thp base
$ numactl --cpunodebind=0 --interleave=0,1 ./observar_thp base
Las restricciones de cpusets y contenedores pueden reducir los nodos y CPU accesibles o impedir llamadas de política. Un error no demuestra que la máquina carezca de NUMA. Verifica las condiciones del entorno antes de sacar conclusiones.
Para control desde C existen set_mempolicy, mbind, interfaces de migración y la biblioteca libnuma. Aquí usaremos afinidad de CPU para mostrar el primer toque sin añadir una dependencia de biblioteca. Si impones una política externa con numactl, esa política también influirá en el experimento.
10. Experimento de inicialización y recorrido
El programa recibe una CPU para inicializar y otra para leer. Desactiva THP en la región para reducir una variable de la comparación, fija el hilo antes de la primera escritura y después cambia su afinidad para el recorrido. Las dos CPU deben pertenecer al conjunto permitido al iniciar el programa.
Se escribe un byte cada 64 bytes y se realizan 16 recorridos sobre 64 MiB. La separación de 64 bytes es una elección del experimento, no una consulta del tamaño real de línea de caché. volatile obliga a realizar las lecturas del recorrido; no interviene como mecanismo de sincronización.
#define _GNU_SOURCE
#include <errno.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
static int leer_cpu(const char *texto, const cpu_set_t *permitidas)
{
char *fin;
errno = 0;
long n = strtol(texto, &fin, 10);
if (errno || fin == texto || *fin != '\0' || n < 0 ||
n >= CPU_SETSIZE || !CPU_ISSET((int) n, permitidas))
return -1;
return (int) n;
}
static int fijar_cpu(int cpu)
{
cpu_set_t conjunto;
CPU_ZERO(&conjunto);
CPU_SET(cpu, &conjunto);
return sched_setaffinity(0, sizeof conjunto, &conjunto);
}
int main(int argc, char *argv[])
{
if ((argc != 3 && argc != 4) ||
(argc == 4 && strcmp(argv[3], "pausa") != 0)) {
fprintf(stderr, "Uso: %s cpu_inicializa cpu_lee [pausa]\n", argv[0]);
return 1;
}
cpu_set_t permitidas;
if (sched_getaffinity(0, sizeof permitidas, &permitidas) == -1) {
perror("sched_getaffinity"); return 1;
}
int inicializa = leer_cpu(argv[1], &permitidas);
int lee = leer_cpu(argv[2], &permitidas);
if (inicializa < 0 || lee < 0) {
fprintf(stderr, "CPU inválida o fuera del conjunto permitido\n");
return 1;
}
size_t tam = 64UL * 1024 * 1024;
unsigned char *p = mmap(NULL, tam, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) { perror("mmap"); return 1; }
int codigo = 1;
if (madvise(p, tam, MADV_NOHUGEPAGE) == -1) {
perror("madvise"); goto liberar;
}
if (fijar_cpu(inicializa) == -1) {
perror("afinidad de inicialización"); goto liberar;
}
for (size_t i = 0; i < tam; i += 64)
p[i] = 1;
if (fijar_cpu(lee) == -1) {
perror("afinidad de lectura"); goto liberar;
}
struct timespec antes, despues;
if (clock_gettime(CLOCK_MONOTONIC, &antes) == -1) {
perror("clock_gettime"); goto liberar;
}
volatile const unsigned char *lectura = p;
uint64_t suma = 0;
for (int vuelta = 0; vuelta < 16; vuelta++)
for (size_t i = 0; i < tam; i += 64)
suma += lectura[i];
if (clock_gettime(CLOCK_MONOTONIC, &despues) == -1) {
perror("clock_gettime"); goto liberar;
}
double segundos = (despues.tv_sec - antes.tv_sec) +
(despues.tv_nsec - antes.tv_nsec) / 1e9;
printf("Inicializa CPU %d; lee CPU %d; tiempo %.6f s; suma %llu\n",
inicializa, lee, segundos, (unsigned long long) suma);
if (argc == 4) {
printf("PID: %ld; base: %p. Pulsa Enter para terminar.\n",
(long) getpid(), (void *) p);
getchar();
}
codigo = suma == (uint64_t) (tam / 64) * 16 ? 0 : 1;
liberar:
if (munmap(p, tam) == -1) { perror("munmap"); codigo = 1; }
return codigo;
}
$ gcc -std=c17 -Wall -Wextra -O2 recorrido_numa.c -o recorrido_numa
$ ./recorrido_numa 0 0
$ ./recorrido_numa 0 8
$ ./recorrido_numa 8 8 pausa
Cada ejecución muestra sus CPU, tiempo medido y suma 16777216.
Para comparar nodos, selecciona CPU que la topología ubique en nodos distintos. Dos CPU diferentes del mismo nodo no constituyen una comparación local/remota. El programa usa un cpu_set_t de tamaño fijo; en sistemas que excedan su capacidad debe adaptarse a conjuntos de CPU dinámicos.
La medida es el tiempo de un recorrido con lecturas independientes, no la latencia pura de DRAM. Puede solapar accesos, aprovechar precarga y leer desde cachés; 64 MiB incluso pueden caber en la caché de último nivel de algunas máquinas. Tampoco controla todas las decisiones de migración. Para estudiar latencia de memoria se necesitan otros patrones, como cadenas de accesos dependientes, y un diseño de medición específico.
11. Simulador de traducción y ubicación
El modelo divide una región en dos mitades: un trabajador del nodo 0 utiliza la primera y otro del nodo 1 utiliza la segunda. Ambos realizan la misma cantidad de accesos por byte útil. Puedes cambiar la página, la cantidad de entradas de TLB y quién asigna inicialmente las páginas.
Simulador · tamaño de página y localidad NUMA
| Páginas | Respaldo redondeado | Bytes sin uso al final | Cobertura teórica TLB |
|---|---|---|---|
| Trabajador | Parte útil | Accesos locales | Accesos remotos |
|---|
Asignación de páginas, primeras 16 como máximo:
| Página | Intervalo útil, MiB | Nodo asignado |
|---|
Con los valores iniciales hay 16 384 páginas de 4 KiB y una cobertura teórica de 256 KiB. Al pasar a 2 MiB quedan 32 páginas y la cobertura sube a 128 MiB. Con inicialización por mitades, ambas particiones conservan localidad porque el límite coincide con páginas completas.
Con una página de 1 GiB, las dos mitades de una región útil de 64 MiB caen en la misma página. En este modelo el trabajador 0 la toca primero, por lo que se coloca en nodo 0 y el trabajador 1 accede remotamente. Además, el respaldo redondeado a páginas completas es de 1 GiB, aunque solo se usen 64 MiB.
El simulador supone páginas uniformes, asignación sin fallos y ausencia de migración. Para una página que cruza el límite entre mitades, el trabajador 0 la materializa primero. No reproduce THP mixtas ni mTHP, y los porcentajes son accesos al respaldo según el patrón indicado: no predicen transacciones reales de DRAM, tiempos ni una tasa exacta de fallos de TLB.
12. Observar páginas y nodos en Linux
Primero consulta la configuración sin cambiarla. La opción entre corchetes en una línea de política indica la seleccionada en esa interfaz:
$ cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never
$ cat /sys/kernel/mm/transparent_hugepage/defrag
$ ls /sys/kernel/mm/transparent_hugepage/hugepages-*/enabled
$ grep -E 'HugePages_|Hugepagesize|Hugetlb|AnonHugePages' /proc/meminfo
$ ls /sys/kernel/mm/hugepages
Los directorios por tamaño de THP dependen de la versión y configuración; su ausencia en un sistema no es un error del programa. HugePages_Total, HugePages_Free y HugePages_Rsvd describen el pool HugeTLB del tamaño predeterminado. AnonHugePages corresponde a otra categoría: no son nombres intercambiables.
Mientras observar_thp o recorrido_numa está en pausa, reemplaza el PID por el que imprime y localiza la región por su dirección inicial:
$ less /proc/4217/smaps
$ cat /proc/4217/numa_maps
... ejemplo de una región anónima, extracto ilustrativo ...
7f5000000000 default anon=16384 dirty=16384 N0=16384 kernelpagesize_kB=4
$ numastat -p 4217
numa_maps permite relacionar una región con su política efectiva y con las páginas presentes en cada nodo. En el extracto, N0=16384 indica páginas contabilizadas en nodo 0; interpreta sus unidades con el tamaño y el tipo de respaldo que informa el sistema. No deduzcas una tasa de accesos remotos solo a partir de la cantidad de páginas: también necesitas saber qué CPU y qué patrón las utilizan.
numastat -p resume la distribución de memoria del proceso. Para estudiar traducciones puede servir perf stat, si los permisos y el hardware exponen contadores apropiados:
$ perf list
$ perf stat -e dTLB-loads,dTLB-load-misses,page-faults ./recorrido_numa 0 0
Los eventos genéricos no tienen idéntica implementación en todos los procesadores. El resultado puede incluir el arranque y la inicialización, mientras que el temporizador del programa cubre solo los recorridos. En una máquina virtual tal vez no haya contadores disponibles. Una reducción de un contador aislado no demuestra por sí sola una mejora del tiempo total.
13. Medir y decidir
Empieza por un caso correcto y repetible. Mantén el mismo trabajo útil y comprueba el resultado; luego cambia una variable por vez. Si comparas páginas base con THP, verifica primero qué respaldo recibió cada ejecución. Si comparas NUMA local con remoto, confirma la topología y la ubicación de las páginas.
- Separar fases. Mide reserva, inicialización y procesamiento de forma que sepas qué costo estás observando.
- Repetir y alternar. Ejecuta varias veces ambas variantes y compara su variación, no solo el mejor tiempo.
- Controlar el entorno. Considera afinidad, políticas, carga de otros procesos, cachés, frecuencia y migraciones.
- Observar varias métricas. Tiempo, uso de memoria, fallos, distribución por nodos y latencias de las operaciones relevantes.
- Evaluar el costo completo. Una mejora media puede convivir con pausas por compactación o con mayor consumo de memoria.
Para datos densos y muy grandes, las páginas grandes pueden resultar útiles. Para estructuras dispersas o aplicaciones sensibles a pausas, sus costos merecen especial atención. Si el problema principal es una inicialización centralizada que coloca todo en un nodo, ampliar páginas no sustituye repartir correctamente el trabajo.
En memoria compartida entre procesos, cada página también tiene una ubicación física en un momento dado. Todos los consumidores no pueden considerarla local a la vez si están en nodos distintos. Las opciones incluyen particionar datos, colocar consumidores cerca, intercalar para repartir ancho de banda o replicar datos de solo lectura cuando el costo lo justifica.
Documentación para contrastar las interfaces: HugeTLB en el núcleo Linux, Transparent Huge Pages, políticas NUMA, madvise, numactl y smaps.
14. Resumen
- Una traducción grande puede cubrir más memoria y reducir la presión sobre la TLB.
- La cobertura teórica es entradas por tamaño de página; la TLB real tiene límites específicos por arquitectura y tamaño.
- HugeTLB utiliza recursos administrados para páginas grandes explícitas; THP puede elegir páginas grandes para regiones elegibles.
- MADV_HUGEPAGE expresa una preferencia, no certifica que toda la región use páginas grandes.
- El costo incluye memoria desperdiciada, bloques físicos contiguos, puesta a cero y posibles trabajos de compactación.
- NUMA hace que el costo de acceso dependa de la relación entre CPU, nodos y páginas.
- Bajo política local, la primera escritura que materializa una página influye en su ubicación.
- Afinidad de CPU y política de memoria son controles distintos; mover el hilo no mueve inmediatamente todos sus datos.
- smaps, numa_maps y numastat permiten comprobar el respaldo y la distribución antes de interpretar tiempos.
- Mejorar traducción y mejorar localidad requieren mediciones separadas y un patrón de trabajo representativo.
15. Preguntas de repaso
Intenta responder antes de abrir las explicaciones.
1. ¿Cuántas páginas de 4 KiB equivalen a 2 MiB?
512. La cuenta es 2 × 1024 × 1024 / 4096. Una página de 2 MiB mapeada como tal puede cubrir ese intervalo con una traducción grande.
2. ¿Qué cobertura teórica tienen 128 entradas para páginas de 2 MiB?
256 MiB. Es una cuenta ideal: no determina por sí sola la tasa real de fallos, la asociatividad ni la cantidad de entradas de la máquina.
3. ¿Una página grande elimina los fallos de caché de datos?
No. El tamaño de página afecta la traducción y la gestión de memoria. Las cachés de datos siguen teniendo sus propias líneas, capacidades y patrones de acceso.
4. ¿MADV_HUGEPAGE con resultado cero prueba que hay THP en toda la región?
No. Informa que se aceptó la indicación. Hay que observar la región y considerar elegibilidad, tamaños, políticas y disponibilidad.
5. ¿Qué diferencia hay entre HugePages_Free y AnonHugePages?
El primero es un contador del pool HugeTLB; el segundo informa memoria anónima respaldada por páginas grandes transparentes en el ámbito del archivo consultado. No miden la misma categoría.
6. ¿Por qué la primera escritura es importante para NUMA?
Porque puede materializar la página privada. Bajo política local, la CPU que provoca esa asignación influye en el nodo elegido. La lectura de ceros puede no crear todavía esa página privada.
7. ¿Cambiar la afinidad de un hilo migra su arreglo?
No por sí solo. Cambia dónde se ejecuta el hilo. La migración de páginas depende de otros mecanismos, políticas y condiciones.
8. ¿Intercalar páginas garantiza accesos locales?
No. Distribuye asignaciones entre nodos. Puede repartir capacidad o ancho de banda, pero un trabajador de un solo nodo tendrá parte de sus accesos al respaldo en otros nodos.
9. ¿Dos CPU distintas bastan para probar acceso remoto?
No. Pueden pertenecer al mismo nodo. Se debe consultar la topología y comprobar dónde residen las páginas que utilizan.
10. ¿Por qué una página muy grande puede empeorar el reparto por trabajadores?
Porque los datos de varios consumidores pueden caer en una misma unidad de ubicación. Si se asigna a un nodo, otros consumidores pueden necesitar acceder remotamente a su parte.
11. ¿El recorrido en C mide latencia pura de DRAM?
No. Incluye un patrón con accesos que pueden solaparse y aprovechar cachés y precarga. Es una medida del recorrido bajo esas condiciones.
Para practicar
- Calcula las páginas necesarias para 3 GiB con páginas de 4 KiB y de 2 MiB. Comprueba que obtienes 786 432 y 1536.
- Calcula la cobertura de 96 entradas con páginas de 4 KiB y de 2 MiB. Explica por qué no puedes convertir directamente esa cuenta en una tasa de aciertos.
- Ejecuta observar_thp en ambos modos y localiza la región en smaps. Registra los campos relevantes aunque el modo thp no produzca páginas grandes.
- Ejecuta hugetlb_2m y clasifica su resultado. Si falla, consulta el estado del pool sin modificarlo y distingue falta de recursos de falta de soporte o permisos.
- Consulta la topología y el conjunto de CPU permitido. Ejecuta recorrido_numa usando dos CPU permitidas y anota si pertenecen al mismo nodo.
- Si hay dos nodos accesibles, compara varias ejecuciones con inicialización y lectura en el mismo nodo frente a lectura desde otro. Registra también la distribución de páginas.
- En el simulador, compara 64 MiB con páginas de 4 KiB, 2 MiB y 1 GiB. Explica cobertura, desperdicio y localidad en cada caso.
- Selecciona inicialización completa en nodo 0 y después intercalado. Explica por qué repartir páginas no garantiza que todos los accesos sean locales.
- Diseña la inicialización de un arreglo dividido entre cuatro trabajadores en dos nodos. Considera los límites de página y qué sucede si una página grande cruza una partición.