Por eso, hoy lo haremos ... .Un minúsculo trabajo de laboratorio. En forma de un pequeño programa en C que escribimos, compilamos y probamos en acción, con y sin intercambio.
El programa hace algo muy simple: solicita una gran cantidad de memoria, accede a ella y trabaja activamente con ella. Para no sufrir con la carga de bibliotecas, simplemente crearemos un archivo grande que se mapeará en la memoria como lo hace el sistema al cargar bibliotecas compartidas.
Y simplemente emulamos la llamada del código de esta "biblioteca" leyendo de dicho archivo mmap.
El programa realizará varias iteraciones, en cada iteración accederá simultáneamente al "código" y una de las secciones de un gran segmento de datos.
Y, para no escribir código innecesario, definiremos dos constantes que determinarán el tamaño del "segmento de código" y el tamaño total de la RAM:
- MEM_GBYTES: el tamaño de la RAM para la prueba
- LIB_GBYTES - tamaño del "código"
La cantidad de "datos" que tenemos es menor que la cantidad de memoria física:
- DATA_GBYTES = MEM_GBYTES - 2
La cantidad total de "código" y "datos" es ligeramente mayor que la cantidad de memoria física:
- DATA_GBYTES + LIB_GBYTES = MEM_GBYTES + 1
Para una prueba en una computadora portátil, tomé MEM_GBYTES = 16 y obtuve las siguientes características:
- MEM_GBYTES = 16
- DATA_GBYTES = 14 - significa que "datos" serán 14 GB, es decir, "memoria suficiente"
- Tamaño de intercambio = 16 GB
Texto del programa
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define GB 1073741824l
#define MEM_SIZE 16
#define LIB_GBYTES 3
#define DATA_GBYTES (MEM_SIZE - 2)
long random_read(char * code_ptr, char * data_ptr, size_t size) {
long rbt = 0;
for (unsigned long i=0 ; i<size ; i+=4096) {
rbt += code_ptr[(8l * random() % size)] + data_ptr[i];
}
return rbt;
}
int main() {
size_t libsize = LIB_GBYTES * GB;
size_t datasize = DATA_GBYTES * GB;
int fd;
char * dataptr;
char * libptr;
srandom(256);
if ((fd = open("library.bin", O_RDONLY)) < 0) {
printf("Required library.bin of size %ld\n", libsize);
return 1;
}
if ((libptr = mmap(NULL, libsize,
PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {
printf("Failed build libptr due %d\n", errno);
return 1;
}
if ((dataptr = mmap(NULL, datasize,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
-1, 0)) == MAP_FAILED) {
printf("Failed build dataptr due %d\n", errno);
return 1;
}
printf("Preparing test ...\n");
memset(dataptr, 0, datasize);
printf("Doing test ...\n");
unsigned long chunk_size = GB;
unsigned long chunk_count = (DATA_GBYTES - 3) * GB / chunk_size;
for (unsigned long chunk=0 ; chunk < chunk_count; chunk++) {
printf("Iteration %d of %d\n", 1 + chunk, chunk_count);
random_read(libptr, dataptr + (chunk * chunk_size), libsize);
}
return 0;
}
Prueba sin usar swap
Deshabilite el intercambio especificando vm.swappines = 0 y ejecute la prueba
$ time ./swapdemo Preparing test ... Killed real 0m6,279s user 0m0,459s sys 0m5,791s
¿Que pasó? El valor de intercambio = 0 deshabilitó el intercambio: las páginas anónimas ya no se insertan en él, es decir, los datos siempre están en la memoria. El problema es que los 2 GB restantes no eran suficientes para que Chrome y VSCode se ejecutaran en segundo plano, y el asesino de OOM eliminó el programa de prueba. Y al mismo tiempo, la falta de memoria enterró la pestaña de Chrome en la que escribí este artículo. Y no me gustó, incluso si el autoguardado funcionó. No me gusta cuando mis datos están enterrados.
Swap incluido
Establezca vm_swappines = 60 (predeterminado)
Ejecute la prueba:
$ time ./swapdemo Preparing test ... Doing test ... Iteration 1 of 11 Iteration 2 of 11 Iteration 3 of 11 Iteration 4 of 11 Iteration 5 of 11 Iteration 6 of 11 Iteration 7 of 11 Iteration 8 of 11 Iteration 9 of 11 Iteration 10 of 11 Iteration 11 of 11 real 1m55,291s user 0m2,692s sys 0m20,626s
Parte superior del fragmento:
Tasks: 298 total, 2 running, 296 sleeping, 0 stopped, 0 zombie %Cpu(s): 0,6 us, 3,1 sy, 0,0 ni, 85,7 id, 10,1 wa, 0,5 hi, 0,0 si, 0,0 st MiB Mem : 15670,0 total, 156,0 free, 577,5 used, 14936,5 buff/cache MiB Swap: 16384,0 total, 12292,5 free, 4091,5 used. 3079,1 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 10393 viking 20 0 17,0g 14,2g 14,2g D 17,3 93,0 0:18.78 swapdemo 136 root 20 0 0 0 0 S 9,6 0,0 4:35.68 kswapd0
Linux malo, malo !!! Utiliza casi 4 gigabytes de intercambio, ¡aunque tiene 14 gigabytes de caché y 3 gigabytes disponibles! ¡Linux tiene una configuración incorrecta! Malo outlingo, malos viejos administradores, no entienden nada, dijeron que habilitaran el intercambio y ahora hacen que el sistema intercambie y funcione mal para mí. Es necesario desactivar el intercambio como aconsejan los expertos en Internet mucho más jóvenes y prometedores, ¡porque saben exactamente qué hacer!
Bueno ... que así sea. ¿Apaguemos el intercambio tanto como sea posible siguiendo el consejo de los expertos?
Prueba casi sin intercambio
Establecemos vm_swappines = 1
Este valor conducirá al hecho de que el intercambio de páginas anónimas se realizará solo si no hay otra salida.
Confío en Chris Down porque creo que es un gran ingeniero y sabe lo que dice cuando explica que el archivo de intercambio hace que el sistema funcione mejor. Por lo tanto, esperando que “algo” saliera mal y el sistema pudiera funcionar terriblemente ineficientemente, me aseguré de antemano y ejecuté el programa de prueba, limitándolo con un temporizador para ver al menos su terminación anormal.
Veamos primero la salida superior:
Tasks: 302 total, 1 running, 301 sleeping, 0 stopped, 0 zombie %Cpu(s): 0,2 us, 4,7 sy, 0,0 ni, 84,6 id, 10,0 wa, 0,4 hi, 0,0 si, 0,0 st MiB Mem : 15670,0 total, 162,8 free, 1077,0 used, 14430,2 buff/cache MiB Swap: 20480,0 total, 18164,6 free, 2315,4 used. 690,5 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6127 viking 20 0 17,0g 13,5g 13,5g D 20,2 87,9 0:10.24 swapdemo 136 root 20 0 0 0 0 S 17,2 0,0 2:15.50 kswapd0
¡¿Hurra ?! El intercambio se usa solo para 2.5 gigabytes, que es casi 2 veces menos que en la prueba con el intercambio habilitado (e intercambio = 60). Swap se usa menos. También hay menos memoria libre. Y probablemente podamos dar la victoria con seguridad a los jóvenes expertos. Pero aquí está lo extraño: nuestro programa nunca pudo completar ni siquiera 1 (¡UNA!) Iteración en 2 (¡DOS!) Minutos:
$ { sleep 120 ; killall swapdemo ; } & [1] 6121 $ time ./swapdemo Preparing test … Doing test … Iteration 1 of 11 [1]+ Done { sleep 120; killall swapdemo; } Terminated real 1m58,791s user 0m0,871s sys 0m23,998s
Repetimos: el programa no pudo completar 1 iteración en 2 minutos, aunque en la prueba anterior hizo 11 iteraciones en 2 minutos, es decir, con el intercambio casi deshabilitado, el programa se ejecuta más de 10 (!) Veces más lento.
Pero hay una ventaja: no se dañó ni una sola pestaña de Chrome. Y esto es bueno.
Prueba con swap completamente inhabilitante
¿Pero tal vez simplemente "aplastar" el intercambio a través del intercambio no es suficiente, y debería desactivarse por completo? Naturalmente, esta teoría también debería probarse. Vinimos aquí para realizar pruebas, ¿o qué?
Este es el caso ideal:
- no tenemos intercambio y todos nuestros datos estarán garantizados en memoria
- el intercambio no se usará ni siquiera accidentalmente, porque no está allí
Y ahora nuestra prueba terminará a la velocidad del rayo, los ancianos irán al lugar que se merecen y cambiarán los cartuchos, el camino para los jóvenes.
Desafortunadamente, el resultado de ejecutar el programa de prueba es similar: ni siquiera se ha completado una iteración.
Salida superior:
Tasks: 217 total, 1 running, 216 sleeping, 0 stopped, 0 zombie %Cpu(s): 0,0 us, 2,2 sy, 0,0 ni, 85,2 id, 12,6 wa, 0,0 hi, 0,0 si, 0,0 st MiB Mem : 15670,0 total, 175,2 free, 331,6 used, 15163,2 buff/cache MiB Swap: 0,0 total, 0,0 free, 0,0 used. 711,2 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 136 root 20 0 0 0 0 S 12,5 0,0 3:22.56 kswapd0 7430 viking 20 0 17,0g 14,5g 14,5g D 6,2 94,8 0:14.94 swapdemo
Por qué está pasando esto
La explicación es muy simple: el "segmento de código" que conectamos a través de mmap (libptr) está en la caché. Por lo tanto, cuando prohibimos (o casi prohibimos) el intercambio de una manera u otra, no importa cómo, al deshabilitar físicamente el intercambio, o mediante vm.swappines = 0 | 1, siempre termina con el mismo escenario: vaciar el archivo mmap del caché y luego cargarlo desde el disco. Y las bibliotecas se cargan exactamente a través de mmap, y para verificar esto, solo necesita hacer ls -l / proc // map_files:
$ ls -l /proc/8253/map_files/ | head -n 10 total 0 lr-------- 1 viking viking 64 7 12:58 556799983000-55679998e000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 55679998e000-5567999af000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 5567999af000-5567999bf000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 5567999c0000-5567999c4000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 5567999c4000-5567999c5000 -> /usr/libexec/gnome-session-binary lr-------- 1 viking viking 64 7 12:58 7fb22a033000-7fb22a062000 -> /usr/share/glib-2.0/schemas/gschemas.compiled lr-------- 1 viking viking 64 7 12:58 7fb22b064000-7fb238594000 -> /usr/lib/locale/locale-archive lr-------- 1 viking viking 64 7 12:58 7fb238594000-7fb2385a7000 -> /usr/lib64/gvfs/libgvfscommon.so lr-------- 1 viking viking 64 7 12:58 7fb2385a7000-7fb2385c3000 -> /usr/lib64/gvfs/libgvfscommon.so
Y, como consideramos en la primera parte del artículo, el sistema, en condiciones de falta real de memoria, cuando el intercambio de páginas anónimas está deshabilitado, elegirá la única opción que dejó el propietario que deshabilitó el intercambio. Y esta opción está recuperando (liberando) páginas en blanco ocupadas por los datos de las bibliotecas cargadas en mmap.
Conclusión
El uso activo del método de distribución de software “Me llevo todo conmigo” (flatpak, snap, docker image) conduce al hecho de que la cantidad de código que se conecta a través de mmap aumenta significativamente.
Esto puede llevar al hecho de que el uso de "optimizaciones extremas" asociadas con la configuración / desactivación del intercambio puede producir efectos completamente inesperados, porque un archivo de intercambio es un mecanismo para optimizar el subsistema de memoria virtual en condiciones de presión de memoria, y la memoria disponible es no completamente "memoria no utilizada" sino la suma de la caché y la memoria libre.
Al deshabilitar el archivo de intercambio, no "elimina la opción incorrecta", sino que "no deja opciones".
Debe tener mucho cuidado al interpretar los datos de consumo de memoria del proceso: VSS y RSS. Representan "estado actual" y no "estado óptimo".
Si no desea que el sistema utilice el intercambio, agregue memoria, pero no desactive el intercambio . Deshabilitar el intercambio en los niveles de umbral hará que la situación sea mucho peor de lo que hubiera sido si el sistema se hubiera intercambiado un poco.
PD: En las discusiones, las preguntas se hacen regularmente "pero si habilita la compresión de memoria a través de zram ...". Sentí curiosidad y ejecuté las pruebas apropiadas: si habilita zram y swap, como se hace de forma predeterminada en Fedora, entonces el tiempo de ejecución se acelera a aproximadamente 1 minuto.
Pero la razón de esto es que las páginas con ceros se comprimen muy bien, por lo que en realidad los datos no se intercambian, sino que se almacenan comprimidos en la RAM. Si llena un segmento de datos con datos aleatorios, poco comprimibles, la imagen se volverá menos espectacular y el tiempo de ejecución de la prueba aumentará nuevamente a 2 minutos, lo que es comparable (e incluso ligeramente peor) que el de un archivo de intercambio "honesto".