1. Elegir la pregunta antes de la herramienta
En el tema anterior distinguimos objetos vivos, capacidad, reservas y retención. Ahora veremos las herramientas que observan esas capas. Una sola columna llamada «memoria» no puede responder todas estas preguntas:
| Pregunta | Medida o fuente apropiada | Qué no responde por sí sola |
|---|---|---|
| ¿Cuánto espacio virtual tiene el proceso? | VmSize, VSZ y mapas | Cuánta RAM está usando ahora |
| ¿Qué páginas están residentes en sus mapeos? | RSS | Qué parte está compartida y se cuenta también en otros procesos |
| ¿Cómo repartir el costo de páginas compartidas? | PSS | Cuántos bytes pidió la aplicación mediante malloc |
| ¿Qué páginas residentes son exclusivas? | USS convencional a partir de smaps | El costo total del proceso o de un contenedor |
| ¿Cuál fue el máximo residente? | VmHWM, ru_maxrss o time | El valor actual después de liberar |
| ¿Qué ruta de llamadas produce el pico de asignación? | Perfil de heap, por ejemplo Massif | Un inventario idéntico al RSS nativo |
Hay que fijar también el ámbito: un proceso, una familia de procesos, varios trabajadores o un cgroup. Las métricas de un contenedor pueden incluir categorías y reglas distintas de la suma de los procesos que vemos dentro de él.
Usaremos KiB y MiB con base 1024. Aunque /proc imprime kB y algunas herramientas dicen «kilobytes», los valores de memoria que usaremos allí se interpretan en unidades de 1024 bytes. En statm, en cambio, las unidades son páginas: hay que multiplicar por el tamaño consultado del sistema.
2. Memoria virtual y RSS
Tamaño virtual es el tamaño de los intervalos de direcciones que se contabilizan para el proceso. RSS, Resident Set Size, describe su conjunto residente: páginas de esos mapeos que están actualmente en RAM según la contabilidad utilizada.
Un mmap anónimo puede ampliar VmSize sin que toda la región se materialice inmediatamente. Una biblioteca mapeada también contribuye al espacio virtual y sus páginas utilizadas pueden entrar en RSS. Las reservas de pilas, archivos mapeados y regiones aún sin tocar ayudan a explicar una diferencia grande entre ambas cifras.
Las páginas que están solo en swap no forman parte del RSS residente. Un acceso puede traerlas de vuelta, por lo que la cifra actual no describe necesariamente el máximo que necesitará la siguiente fase. VmSize tampoco equivale al compromiso global de memoria ni garantiza que cualquier acceso futuro pueda satisfacerse.
| Campo de status | Interpretación orientativa |
|---|---|
| VmSize | Tamaño virtual contabilizado |
| VmRSS | Residencia actual aproximada según esos contadores |
| RssAnon | Parte residente de respaldo anónimo |
| RssFile | Parte residente respaldada por archivos |
| RssShmem | Parte residente de memoria compartida de las categorías correspondientes |
| VmSwap | Intercambio de memoria anónima privada en esa contabilidad; no todo swap compartido |
La clasificación por respaldo no es una prueba de compartición efectiva: RssFile no significa que todas esas páginas estén siendo utilizadas por otro proceso. Un mapeo privado puede compartir páginas limpias de un archivo, y un mapeo compartido puede tener un único usuario en un momento dado.
3. PSS, USS y páginas compartidas
Si dos procesos tienen presente la misma página física, su RSS puede contarla en ambos. Por eso sumar RSS no obtiene automáticamente el número de bytes físicos distintos.
PSS, Proportional Set Size, reparte la contribución de una página compartida entre quienes la mapean según las reglas del núcleo. En el modelo sencillo de una página compartida por N procesos una vez cada uno, aporta tamaño / N a cada PSS.
Cada proceso: 10 MiB privados residentes.
Los dos comparten los mismos 20 MiB residentes.
RSS de A = 10 + 20 = 30 MiB
RSS de B = 10 + 20 = 30 MiB
Suma de RSS = 60 MiB
PSS de A = 10 + 20 / 2 = 20 MiB
PSS de B = 10 + 20 / 2 = 20 MiB
Suma de PSS = 40 MiB
Contenido residente físico distinto del ejemplo = 40 MiB
USS, Unique Set Size, se usa convencionalmente para el residente exclusivo. Para páginas normales se suele calcular como Private_Clean + Private_Dirty de smaps. En el ejemplo, USS de cada proceso es 10 MiB.
Si B termina, las páginas comunes que A conserve pueden pasar a ser exclusivas de A: su RSS puede seguir en 30 MiB mientras su PSS y USS aumentan. Eso no significa que A haya solicitado nuevos objetos. Compartir es una relación que puede cambiar sin modificar el número de bytes de usuario.
Las sumas necesitan un ámbito completo y observaciones comparables. Si parte de la compartición ocurre con procesos fuera del grupo medido, la suma de PSS del grupo solo recibe su fracción. El núcleo también tiene detalles de mapcount, mapeos múltiples, páginas grandes y redondeo que el ejemplo no reproduce.
RSS, PSS y la USS convencional no representan todo el costo físico de existir como proceso: por ejemplo, no incluyen del mismo modo tablas de páginas y estructuras del núcleo. Las páginas HugeTLB tienen campos particulares, como Private_Hugetlb y Shared_Hugetlb, y pueden quedar fuera de contadores habituales; hay que decidir explícitamente si se incluyen.
4. El mapa de archivos de /proc
| Archivo | Contenido | Uso |
|---|---|---|
| /proc/PID/maps | Intervalos, permisos y respaldo | Localizar una región, sin deducir su residencia solo por el tamaño |
| /proc/PID/status | Resumen legible con VmSize, VmRSS y otros campos | Fotografía rápida con limitaciones de exactitud |
| /proc/PID/statm | Columnas expresadas en páginas | Resumen compacto; algunas columnas son aproximadas o históricas |
| /proc/PID/smaps | Estadísticas por mapeo | Relacionar tamaño, RSS, PSS, limpieza y respaldo de una región |
| /proc/PID/smaps_rollup | Agregación de las estadísticas pertinentes del proceso | Obtener totales sin procesar todas las regiones por separado |
$ grep -E 'VmSize|VmRSS|VmHWM|VmSwap|RssAnon|RssFile|RssShmem' /proc/4217/status
$ cat /proc/4217/maps
$ cat /proc/4217/smaps_rollup
$ less /proc/4217/smaps
En smaps, Clean y Dirty indican propiedades del contenido, no objetos libres y ocupados de malloc. Una página Private_Dirty puede contener metadatos o bloques ya liberados que el asignador conserva. Anonymous identifica respaldo, no una fuga.
Los contadores rápidos de status y statm tienen aproximaciones de escalabilidad. El recorrido más detallado de smaps es más costoso. smaps_rollup facilita obtener totales, pero tampoco debe consultarse en un bucle de alta frecuencia como si fuera una lectura gratuita.
La disponibilidad depende del núcleo y su configuración. Los permisos de proc, restricciones de inspección, namespaces y la terminación del proceso pueden impedir una lectura. Si rollup no está disponible, se puede agregar cada campo de smaps, teniendo cuidado de no sumar dos veces totales que no pertenecen al mismo nivel.
5. Fotografías con ps y pmap
$ ps -p 4217 -o pid,comm,vsz,rss,pmem
$ pmap -x 4217
$ pmap -X 4217
En ese formato de ps, VSZ es el tamaño virtual y RSS se expresa en KiB. %MEM relaciona una medida residente con memoria física según la herramienta; no es el porcentaje de heap vivo ni debe suponerse que usa como denominador el límite de un contenedor.
pmap -x muestra un mapa extendido con columnas de tamaño y residencia. pmap -X expone más detalles que dependen de smaps y de la versión. Lee la cabecera efectiva antes de interpretar una columna o escribir un parser.
Una línea anónima grande puede ser una reserva sin tocar, un heap, una pila u otra región; el tamaño por sí solo no la clasifica. Y [heap] no contiene necesariamente todos los malloc, como vimos en los temas 45 y 47.
No sumes las filas de varios hilos como si cada una fuese un proceso con espacio independiente. Los hilos de un proceso comparten sus mapeos, y algunas herramientas repiten cifras del proceso al mostrarlos.
6. Valores actuales y máximos históricos
Un pico conserva el mayor valor registrado. No tiene por qué bajar al liberar, descartar páginas o terminar una fase. En Linux, getrusage permite observar el máximo residente y los fallos acumulados:
struct rusage uso;
if (getrusage(RUSAGE_SELF, &uso) == 0) {
printf("Máximo RSS: %ld KiB; menores: %ld; mayores: %ld\n",
uso.ru_maxrss, uso.ru_minflt, uso.ru_majflt);
}
Las unidades de ru_maxrss no son idénticas en todas las plataformas; aquí hablamos de Linux. RUSAGE_SELF se refiere al proceso, con sus hilos, no a sumar arbitrariamente sus hijos. RUSAGE_CHILDREN tiene otra semántica y no representa una fotografía actual de todos los procesos descendientes.
$ /usr/bin/time -v ./fases_memoria
... tiempo y estadísticas de la ejecución ...
Maximum resident set size (kbytes): ...
Minor (reclaiming a frame) page faults: ...
Major (requiring I/O) page faults: ...
La ruta evita confundir esta herramienta con la palabra reservada time de una shell. Los fallos son acumulados: para estudiar una fase se toman diferencias entre dos lecturas. Un fallo menor no implica lectura de disco ni una asignación adicional de la aplicación.
Un muestreo periódico puede perder un pico breve entre muestras. El máximo del sistema y el máximo estimado por un perfilador tampoco tienen por qué coincidir: no miden necesariamente la misma capa ni usan la misma política de captura.
7. Experimento: reservar, tocar, descartar y desmapear
El programa crea una región inicialmente inaccesible, habilita lectura y escritura sin tocarla, escribe en la mitad y después en toda ella, descarta el respaldo con MADV_DONTNEED y finalmente la desmapea. En cada fase muestra contadores de smaps_rollup y getrusage.
Usamos memoria anónima privada y desactivamos THP en esta región para reducir una variable. MADV_DONTNEED descarta su contenido: no es una forma de conservar datos importantes mientras se reduce su RSS. El siguiente acceso autorizado a esos bytes anónimos puede observar ceros.
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <unistd.h>
static int foto(const char *fase, int pausa)
{
printf("\nFASE: %s\n", fase);
struct rusage uso;
if (getrusage(RUSAGE_SELF, &uso) == -1) { perror("getrusage"); return 1; }
printf("maxrss=%ld KiB; minflt=%ld; majflt=%ld\n",
uso.ru_maxrss, uso.ru_minflt, uso.ru_majflt);
FILE *f = fopen("/proc/self/smaps_rollup", "r");
if (f == NULL) { perror("smaps_rollup"); return 1; }
char linea[256], clave[64];
unsigned long long valor;
while (fgets(linea, sizeof linea, f) != NULL) {
if (sscanf(linea, "%63[^:]: %llu kB", clave, &valor) == 2 &&
(strcmp(clave, "Rss") == 0 || strcmp(clave, "Pss") == 0 ||
strcmp(clave, "Private_Clean") == 0 || strcmp(clave, "Private_Dirty") == 0 ||
strcmp(clave, "Swap") == 0))
printf("%s=%llu KiB\n", clave, valor);
}
int error = ferror(f);
if (fclose(f) != 0) error = 1;
if (pausa) { puts("Pulsa Enter para continuar."); getchar(); }
return error ? 1 : 0;
}
int main(int argc, char *argv[])
{
if (argc > 2 || (argc == 2 && strcmp(argv[1], "pausa") != 0)) {
fprintf(stderr, "Uso: %s [pausa]\n", argv[0]); return 1;
}
setvbuf(stdout, NULL, _IONBF, 0);
int pausa = argc == 2;
long n = sysconf(_SC_PAGESIZE);
if (n <= 0) { fputs("Página no disponible\n", stderr); return 1; }
size_t pagina = (size_t) n;
size_t paginas = (64UL * 1024 * 1024) / pagina;
if (paginas < 2) { fputs("Página demasiado grande para la prueba\n", stderr); return 1; }
size_t tam = paginas * pagina;
printf("PID: %ld; página: %zu; región: %zu bytes\n", (long) getpid(), pagina, tam);
int error = foto("inicio", pausa);
unsigned char *p = mmap(NULL, tam, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (p == MAP_FAILED) { perror("mmap"); return 1; }
printf("Base del mapeo: %p\n", (void *) p);
error |= foto("reserva inaccesible", pausa);
if (mprotect(p, tam, PROT_READ | PROT_WRITE) == -1 ||
madvise(p, tam, MADV_NOHUGEPAGE) == -1) {
perror("preparar región"); munmap(p, tam); return 1;
}
error |= foto("RW sin tocar", pausa);
volatile unsigned char *escritura = p;
for (size_t i = 0; i < paginas / 2; i++) escritura[i * pagina] = 1;
error |= foto("primera mitad escrita", pausa);
for (size_t i = paginas / 2; i < paginas; i++) escritura[i * pagina] = 2;
error |= foto("toda la región escrita", pausa);
if (madvise(p, tam, MADV_DONTNEED) == -1) {
perror("MADV_DONTNEED"); munmap(p, tam); return 1;
}
error |= foto("contenido descartado, mapeo conservado", pausa);
if (munmap(p, tam) == -1) { perror("munmap"); return 1; }
p = NULL;
error |= foto("región desmapeada", pausa);
return error ? 1 : 0;
}
$ gcc -std=c17 -Wall -Wextra -O2 fases_memoria.c -o fases_memoria
$ ./fases_memoria pausa
$ ps -p 4217 -o pid,vsz,rss
$ pmap -x 4217
En un entorno sin presión, es esperable que la reserva eleve el tamaño virtual, las escrituras aumenten la residencia y el descarte la reduzca manteniendo la región. El máximo histórico no tiene por qué bajar. Los valores concretos incluyen bibliotecas, pila e instrumentación y deben observarse, no copiarse de una salida fija.
Después del descarte no tocamos de nuevo los bytes antes de la fotografía, para no materializarlos otra vez. Si falta smaps_rollup el programa informa el error: en ese sistema debes adaptar la lectura a smaps. Una caída por descarte aquí no demuestra que se pueda aplicar esa operación a cualquier bloque de malloc sin perder datos o corromper el asignador.
8. Leer RSS, PSS y USS desde C
Este lector recibe un PID positivo y consulta su smaps_rollup. Calcula la USS convencional de páginas normales y muestra swap por separado. No incluye un recorrido de objetos ni suma a escondidas categorías de HugeTLB.
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc != 2) { fprintf(stderr, "Uso: %s PID\n", argv[0]); return 1; }
char *fin;
errno = 0;
long pid = strtol(argv[1], &fin, 10);
if (errno || fin == argv[1] || *fin != '\0' || pid <= 0 || pid > INT_MAX) {
fputs("PID inválido\n", stderr); return 1;
}
char ruta[64];
snprintf(ruta, sizeof ruta, "/proc/%ld/smaps_rollup", pid);
FILE *f = fopen(ruta, "r");
if (f == NULL) { perror(ruta); return 1; }
unsigned long long rss = 0, pss = 0, limpio = 0, sucio = 0, swap = 0;
unsigned campos = 0;
char linea[256], clave[64];
unsigned long long valor;
while (fgets(linea, sizeof linea, f) != NULL) {
if (sscanf(linea, "%63[^:]: %llu kB", clave, &valor) != 2) continue;
if (strcmp(clave, "Rss") == 0) { rss = valor; campos |= 1; }
else if (strcmp(clave, "Pss") == 0) { pss = valor; campos |= 2; }
else if (strcmp(clave, "Private_Clean") == 0) { limpio = valor; campos |= 4; }
else if (strcmp(clave, "Private_Dirty") == 0) { sucio = valor; campos |= 8; }
else if (strcmp(clave, "Swap") == 0) { swap = valor; campos |= 16; }
}
int error = ferror(f);
if (fclose(f) != 0) error = 1;
if (error || campos != 31 || limpio > ULLONG_MAX - sucio) {
fputs("Lectura incompleta o campos no disponibles\n", stderr); return 1;
}
printf("PID %ld: RSS=%llu KiB; PSS=%llu KiB; USS=%llu KiB; Swap=%llu KiB\n",
pid, rss, pss, limpio + sucio, swap);
return 0;
}
$ gcc -std=c17 -Wall -Wextra -O2 leer_memoria.c -o leer_memoria
$ ./leer_memoria 4217
PID 4217: RSS=... KiB; PSS=... KiB; USS=... KiB; Swap=... KiB
El resultado no es atómico respecto de todos los procesos del sistema y puede cambiar entre lecturas. Si el PID desaparece, se reutiliza o no tienes permiso, no debe tratarse un fallo como «uso cero». En un monitor sostenido también hay que identificar el proceso de manera estable, no solo conservar un número de PID.
Swap no se añade a RSS como si fuera RAM. Si se estudia memoria compartida intercambiada, el campo SwapPss, cuando está disponible, ofrece otra contabilidad proporcional. El programa muestra solamente el campo Swap que leyó y no lo denomina memoria física total.
9. Qué mide Valgrind Massif
Massif es un perfilador de uso de memoria que puede atribuir asignaciones a rutas de llamadas. En su modo habitual sigue el heap instrumentado y estima espacio adicional de administración y alineación. Las pilas no se incluyen por defecto; se pueden habilitar con --stacks=yes.
Ese perfil no es el RSS nativo del proceso. Valgrind instrumenta la ejecución y puede sustituir o interceptar funciones de asignación, con cambios de rendimiento y organización. El pico de heap de Massif no debe etiquetarse como «máxima RAM real».
Con --pages-as-heap=yes, Massif cambia la unidad de observación a asignaciones de páginas como mmap y otras fuentes. Es útil para regiones que no pasan por malloc o asignadores propios, pero no revela automáticamente cada objeto dentro de nuestro pool. Ese modo tampoco mide qué páginas están residentes y no se combina con el perfilado de pilas.
Massif ayuda a localizar fases y responsables de crecimiento, incluso cuando todavía hay referencias a objetos retenidos. No es lo mismo que una comprobación de bloques perdidos o accesos inválidos. Memcheck es otra herramienta de Valgrind para investigar esas clases de problemas.
10. Un programa para localizar el pico
El siguiente programa mantiene un índice de 256 KiB y una caché de 128 KiB. Cada lote agrega temporalmente 16 bloques de 32 KiB y luego los libera. Los tamaños útiles alcanzan 917 504 bytes en la fase con los temporales, además de cualquier asignación de biblioteca.
#include <stdio.h>
#include <stdlib.h>
#define INDICE (256UL * 1024)
#define CACHE (128UL * 1024)
#define TEMPORAL (32UL * 1024)
#define BLOQUES 16
static void tocar(unsigned char *p, size_t n, unsigned char valor)
{
volatile unsigned char *v = p;
for (size_t i = 0; i < n; i += 1024) v[i] = valor;
v[n - 1] = valor;
}
static unsigned char *cargar_indice(void)
{
unsigned char *p = malloc(INDICE);
if (p != NULL) tocar(p, INDICE, 3);
return p;
}
static unsigned char *crear_cache(void)
{
unsigned char *p = malloc(CACHE);
if (p != NULL) tocar(p, CACHE, 5);
return p;
}
static int procesar_lote(const unsigned char *indice,
const unsigned char *cache, unsigned long *suma)
{
unsigned char *temporales[BLOQUES] = { 0 };
int error = 0;
for (size_t i = 0; i < BLOQUES; i++) {
temporales[i] = malloc(TEMPORAL);
if (temporales[i] == NULL) { error = 1; goto limpiar; }
tocar(temporales[i], TEMPORAL, 7);
}
for (size_t i = 0; i < BLOQUES; i++)
*suma += temporales[i][0] + temporales[i][TEMPORAL - 1];
*suma += indice[0] + cache[CACHE - 1];
limpiar:
for (size_t i = 0; i < BLOQUES; i++) free(temporales[i]);
return error;
}
int main(void)
{
unsigned char *indice = cargar_indice();
unsigned char *cache = crear_cache();
if (indice == NULL || cache == NULL) {
free(cache); free(indice); fputs("No se pudo iniciar\n", stderr); return 1;
}
unsigned long suma = 0;
int error = 0;
for (int lote = 0; lote < 3; lote++) {
if (procesar_lote(indice, cache, &suma) != 0) { error = 1; break; }
}
free(cache);
free(indice);
if (error || suma != 696) { fputs("Carga incompleta\n", stderr); return 1; }
printf("Tres lotes completados; suma=%lu\n", suma);
return 0;
}
$ gcc -std=c17 -Wall -Wextra -O0 -g carga_massif.c -o carga_massif
$ ./carga_massif
Tres lotes completados; suma=696
$ valgrind --tool=massif --time-unit=B --massif-out-file=massif.heap.out ./carga_massif
$ ms_print massif.heap.out
La compilación sin optimización facilita reconocer las funciones y líneas de este ejemplo. No reproduce el costo temporal de una compilación optimizada. Si perfilas una versión de producción, conserva símbolos e interpreta inlining y optimizaciones.
El chequeo de suma y las escrituras observables evitan que la prueba sea solo una secuencia sin uso de los objetos. La separación de 1024 bytes es un patrón de acceso del ejemplo, no el tamaño de página. El pico lógico se deduce del código, pero el pico de snapshots que capture Massif puede diferir por sus umbrales y política de muestreo.
11. Interpretar el perfil de Massif
ms_print produce un gráfico de evolución y fotografías detalladas con árboles de asignación. Busca las rutas cargar_indice, crear_cache y procesar_lote. En una fotografía con temporales vivos, la tercera explica la contribución adicional de esa fase.
| Dato del perfil | Qué leer | Qué no inferir |
|---|---|---|
| Heap útil | Tamaños de asignaciones que sigue la herramienta | RSS nativo ni todas las reservas internas del asignador |
| Heap adicional | Estimación de alineación y administración según el modelo de Massif | Medición exacta de las cabeceras de glibc instalada |
| Stacks | Uso de pila si se activó su observación | Algo incluido obligatoriamente en el modo predeterminado |
| Árbol de asignación | Rutas que explican el espacio de una fotografía | Un diagnóstico automático de que esa memoria sea innecesaria |
| Eje con --time-unit=B | Progreso basado en actividad de asignación y liberación | Segundos de reloj o RSS en bytes |
El valor B del eje permite comparar progreso de asignaciones sin interpretar los tiempos lentos bajo instrumentación como rendimiento nativo. Otras unidades tienen otra semántica; registra la elegida junto con el resultado.
$ valgrind --tool=massif --pages-as-heap=yes --massif-out-file=massif.pages.out ./carga_massif
$ ms_print massif.pages.out
Este segundo modo no debe compararse como si fuera otra escala de la misma serie: cambió qué se contabiliza. Para el asignador propio del tema 46, podría mostrar la región de respaldo; no sabría por sí solo cuáles de sus bloques internos están vivos.
Al terminar una fase, el heap observado por Massif puede bajar mientras el RSS de una ejecución nativa no lo hace por retención o contabilidad diferente. Para investigar errores de memoria ejecuta Memcheck por separado, por ejemplo valgrind --tool=memcheck --leak-check=full ./carga_massif; su objetivo no es dibujar la misma curva.
12. Simulador de contabilidad compartida
El modelo tiene procesos independientes, cada uno con su memoria privada residente y en swap, y todos mapean una misma región compartida residente. También pueden reservar direcciones sin tocarlas. No se simulan bibliotecas adicionales, metadatos, alias ni copy-on-write.
Simulador · sumar RSS o repartir PSS
| Proceso | Virtual, MiB | RSS, MiB | PSS, MiB | USS, MiB | Swap, MiB |
|---|
| Suma RSS | Suma PSS | Residente físico distinto del modelo | Suma swap privado |
|---|---|---|---|
Con los valores iniciales, cada proceso tiene 130 MiB virtuales, 30 de RSS, 20 de PSS y 10 de USS. La suma de RSS es 60, pero el contenido residente distinto es 40. Aumentar solo la reserva sin tocar cambia el tamaño virtual, no la residencia del modelo.
Prueba un solo proceso: ahora el único usuario de la región común también la tiene en exclusiva; su PSS y USS incluyen ese contenido. Con más procesos, el PSS de cada uno puede bajar aunque su RSS siga igual. El modelo recrea un conjunto con esas propiedades; cambiar el número no representa un fork real ni conserva su historial.
Las cifras son aritméticas ideales y no predicen el total físico del sistema. El swap privado se suma al espacio virtual reservado y se muestra aparte de los residentes. La igualdad entre suma PSS y contenido residente distinto se cumple aquí porque el modelo incluye a todos los usuarios y omite otras categorías y redondeos.
13. Método de medición y errores frecuentes
- Definir el ámbito y la unidad. Proceso, grupo o cgroup; bytes, KiB o páginas.
- Conocer el trabajo útil. Registrar la cantidad de objetos y bytes vivos cuando esa sea la pregunta.
- Separar fases. Reserva, primer toque, carga estable, liberación y reposo no son el mismo estado.
- Elegir la fuente. RSS rápido, smaps detallado, pico histórico o perfil de asignación según la pregunta.
- Controlar el costo de observar. Repetir lecturas de smaps y perfilar cambia el costo de la ejecución.
- Comparar observaciones equivalentes. Misma versión, configuración, patrón, momento y conjunto de procesos.
| Conclusión apresurada | Qué revisar |
|---|---|
| «VSZ son los bytes de RAM usados» | Reservas sin tocar, mapeos y residencia |
| «La suma de RSS es el costo físico del grupo» | Compartición y ámbito del PSS |
| «RssFile siempre es compartido por otros» | Respaldo frente a compartición efectiva |
| «free no bajó el máximo, por tanto hay fuga» | Un máximo no mide el presente; además puede haber retención |
| «Massif no coincide con ps, alguno está mal» | Capas distintas e instrumentación |
| «El PID no se pudo leer, entonces usa cero» | Permisos, terminación y reutilización del identificador |
Para un grupo de procesos, tomar fotografías una detrás de otra no crea una instantánea global: mientras lees uno, los demás pueden cambiar. Puedes coordinar pausas en una carga controlada, como en el experimento de hilos del tema 47, pero un monitor de producción debe reconocer esa incertidumbre.
El siguiente bloque integra traducción, TLB y reemplazo en un simulador, y después retomará el asignador propio con pruebas. Estas herramientas permiten observar implementaciones reales y verificar qué parte de un modelo simplificado aparece efectivamente en la ejecución.
Documentación de referencia: proc en el núcleo Linux, smaps, status, statm, ps, pmap, getrusage, time y el manual oficial de Massif.
14. Resumen
- La medida correcta depende de la pregunta: reserva virtual, residencia, compartición, objetos o pico.
- RSS puede contar las mismas páginas en varios procesos; PSS reparte su contribución.
- La USS convencional de páginas normales suma Private_Clean y Private_Dirty, con límites de cobertura explícitos.
- status y statm ofrecen resúmenes con aproximaciones; smaps aporta detalle y rollup facilita los totales.
- Los máximos históricos no bajan porque una fase actual consuma menos.
- Reservar, habilitar acceso y escribir páginas son etapas diferentes del uso de memoria.
- Descartar respaldo puede reducir residencia sin retirar el mapeo, y puede perder contenido.
- Massif perfila asignaciones e identifica rutas; su heap no es el RSS de una ejecución nativa.
- Cambiar a pages-as-heap cambia qué se observa, sin producir un inventario de objetos internos.
- Ámbito, unidades, permisos, muestreo e instrumentación forman parte de cualquier interpretación.
15. Preguntas de repaso
Intenta responder antes de abrir las explicaciones.
1. ¿Puede aumentar VmSize sin aumentar de igual forma RSS?
Sí. Reservar regiones sin tocar o mantener otras partes no residentes puede aumentar el espacio virtual sin materializar toda esa capacidad en RAM.
2. ¿Por qué sumar RSS puede contar de más?
Porque una misma página física compartida puede contribuir al residente de varios procesos. La suma no identifica páginas físicas distintas.
3. ¿Qué hace PSS con una página de 4 KiB compartida una vez por cuatro procesos?
En el modelo sencillo aporta 1 KiB al PSS de cada uno, en lugar de contar 4 KiB completos para cada proceso.
4. ¿Cómo puede aumentar PSS sin una nueva reserva?
Si otro usuario de una página compartida desaparece, la contribución proporcional de quienes quedan puede aumentar aunque conserven el mismo mapeo y RSS.
5. ¿Private_Dirty equivale a objetos vivos de malloc?
No. Puede incluir bloques libres retenidos, metadatos, pilas y otras páginas privadas modificadas. Es una clasificación del mapeo, no un censo de objetos.
6. ¿Por qué ru_maxrss no baja después de munmap?
Porque es un máximo histórico. Para observar la residencia actual se necesita otra medida.
7. ¿statm expresa sus columnas en KiB?
No. Las expresa en páginas. Hay que multiplicar por el tamaño de página y considerar que algunas columnas son aproximadas o históricas.
8. ¿MADV_DONTNEED en el experimento conserva los datos importantes?
No. En ese mapeo anónimo privado descarta el contenido y los accesos posteriores pueden recibir ceros. No es una operación genérica sobre bloques de malloc.
9. ¿Massif mide por defecto la misma memoria que RSS?
No. Perfila el heap instrumentado con su modelo de administración. El RSS nativo contabiliza páginas residentes de otra ejecución y otra organización.
10. ¿Qué cambia --pages-as-heap=yes?
El perfil pasa a seguir asignaciones de páginas en lugar del heap habitual. No mide residencia ni descubre por sí solo cada subasignación dentro de un pool.
11. ¿PSS de un subconjunto de procesos suma todo el contenido compartido que usan?
No necesariamente. Si hay usuarios fuera del conjunto, una parte de la contribución proporcional queda atribuida a esos otros usuarios.
12. ¿Un fallo de lectura de proc debe registrarse como consumo cero?
No. Es un dato no disponible que puede deberse a permisos, configuración o finalización. Sustituirlo por cero oculta el problema.
Para practicar
- Ejecuta fases_memoria con pausas. Registra VSZ, RSS y PSS después de reservar, tocar, descartar y desmapear.
- Compara minflt entre dos fases sin tomar el total acumulado como número de fallos exclusivo de una de ellas.
- Consulta el mismo PID con ps, pmap y leer_memoria. Explica diferencias de cobertura, momento y precisión.
- Ejecuta /usr/bin/time -v y compara su máximo con la última fotografía del programa. Explica por qué no deben coincidir necesariamente.
- Usa los programas de memoria compartida del tema 43 y observa sus mapas y PSS mientras ambos procesos mantienen accesible una región.
- En el simulador, usa cuatro procesos con 12 MiB privados y 24 comunes. Comprueba 36 de RSS y 18 de PSS por proceso.
- Aumenta solo las reservas sin tocar del modelo y después el swap. Distingue los cambios virtuales de los residentes.
- Si tienes Valgrind, perfila carga_massif y localiza las funciones responsables del pico. Registra el modo y la unidad del eje.
- Compara un perfil normal con pages-as-heap y explica por qué las cifras no describen la misma magnitud.
- Diseña una tabla de resultados que separe bytes útiles, RSS actual, PSS y máximo histórico, incluyendo una columna para lecturas no disponibles.