Cómo y por qué utilizar ZRAM como "espacio de intercambio" o SWAP

Advertencia

Este artículo contiene algunas erratas. Puedes ver los detalles al final del texto.

Ya he nombrado ZRAM en artículos anteriores. Hoy toca abordarlo en profundidad. Puedes empezar leyendo su página en la wikipedia. Pongo el enlace a la versión en inglés porque la versión en español es vergonzosa.

ZRAM es un servicio disponible en Linux (otros sistemas operativos tienen servicios similares) que permite utilizar parte de la memoria RAM como un disco duro virtual, más exactamente como un "dispositivo de bloques".

Nota

En los viejos tiempos esto permitía, por ejemplo, usar esa memoria RAM que no estás utilizando como un disco ram en el que copiar los datos desde uno o más discos flexibles y así trabajar con más rapidez y comodidad. En mis tiempos de Atari ST con mis cuatro megabytes de RAM, podía dedicar dos megabytes a meter el contenido de dos o tres discos de 3.5 pulgadas en memoria y trabajar a toda velocidad sin tener que esperar al disco ni, más importante, tener que sacar y meter uno u otro disco flexible todo el tiempo. Podías tener en el disco ram el editor, el compilador, el enlazador y el programa binario final, mientras el código fuente que estabas escribiendo residía en un disco flexible.

La tentación de grabar el código fuente también en el disco ram era fuerte... Tengo algunas anécdotas que contar al respecto, sobre lo buena que era la fuente de alimentación de mi Atari ST ante microcortes de energía eléctrica...

Una peculiaridad interesante de ZRAM es que le indicamos un tamaño determinado, pero los datos se almacenan de forma comprimida y, por tanto, su consumo de RAM es dinámico. Esto es interesante, porque un ZRAM sin usar apenas ocupa memoria por mucho tamaño que le hayamos dado. Por otro lado, si los datos se comprimen bien, el ZRAM ocupa menos memoria que los datos originales.

Esta característica de compresión de datos permite un uso interesante de ZRAM: utilizarlo como espacio de intercambio o swap.

La idea es la siguiente: supongamos que tenemos 1GB de memoria RAM y usamos un ZRAM de 1GB (por ejemplo) como espacio de intercambio. Mientras el sistema operativo no hace swap, el ZRAM no ocupa espacio; tenemos toda la RAM para el usuario. A medida que vamos gastado RAM y la agotamos, el sistema operativo empieza a enviar las páginas de memoria que hace más tiempo que no se usan al espacio de intercambio proporcionado por la ZRAM. Esto libera memoria, pero, simultáneamente, la ZRAM crece y ocupa memoria. A priori no parece que ganemos nada, salvo por el hecho de que los datos se comprimen, así que se libera más RAM de la que consume el ZRAM. Por ejemplo, si la compresión es 2:1 y se envían 400 megabytes al ZRAM, esos datos ocuparán 200 megabytes, y se liberarán los otros 200 megabytes de RAM, obteniendo una ganancia neta de 200 megabytes.

Es decir, si tenemos 1 GB de RAM, configuramos un ZRAM de 1 GB y la compresión media es 2:1, podemos trabajar casi como si realmente tuviésemos 2 GB de RAM.

Que esto funcione bien o mal depende solo de lo único que importa cuando hablamos de espacio de intercambio: todo irá bien mientras la cantidad de memoria RAM disponible sea lo bastante grande como para abarcar todo nuestro Working Set.

Uso de dispositivos ZRAM como espacio de intercambio

  1. Importamos el módulo ZRAM en el sistema operativo. Aquí indicamos el número de dispositivos ZRAM que deseamos. Por lo general será uno [1], pero podemos crear varios, cada uno de diferentes tamaños:

    modprobe zram num_devices=4
    
    [1]

    Aunque los dispositivos ZRAM se pueden resetear y reconfigurar en cualquier momento, su número no se puede cambiar sin eliminarlos todos primero. Por eso recomiendo crear varios dispositivos ZRAM aunque solo vayamos a necesitar uno. Los que no se usen no ocupan sitio y cualquiera sabe si nos pueden venir bien más tarde.

  2. A continuación podemos indicar qué algoritmo de compresión queremos emplear y dar un tamaño máximo a cada dispositivo ZRAM [2]:

    echo lz4 >/sys/block/zram0/comp_algorithm
    echo lz4hc >/sys/block/zram1/comp_algorithm
    echo zstd >/sys/block/zram2/comp_algorithm
    
    echo 4294967296 >/sys/block/zram0/disksize
    echo 4294967296 >/sys/block/zram1/disksize
    echo 4294967296 >/sys/block/zram2/disksize
    echo 4294967296 >/sys/block/zram3/disksize
    
  3. Ahora marcamos los dispositivos ZRAM para que puedan ser usados como espacio de intercambio:

    mkswap /dev/zram0
    mkswap /dev/zram1
    mkswap /dev/zram2
    mkswap /dev/zram3
    
  4. Por último, añadimos esos dispositivos ZRAM al espacio de intercambio del sistema operativo:

    swapon -p 5 /dev/zram0
    swapon -p 5 /dev/zram1
    swapon -p 5 /dev/zram2
    swapon -p 5 /dev/zram3
    

Los comandos anteriores crean cuatro dispositivos ZRAM de cuatro gigabytes cada uno, con algoritmos de compresión diferentes. Si no indicamos un algoritmo de compresión concreto, se utiliza un tipo de compresión por defecto, que en mi sistema operativo resulta ser "lzo-rle".

[2] (1, 2, 3) Yo soy viejuno y configuro los dispositivos ZRAM como aprendí en su momento, pero hoy en día el comando zramctl es mucho más simple y evidente. Puedes resetear y reconfigurar un dispositivo ZRAM en una sola línea, indicando su tamaño, el número de streams [3] y el algoritmo de compresión. Mira el manual de uso con man zramctl.

¿Qué tal funciona la cosa?

El motivo para usar cuatro dispositivos ZRAM con cuatro algoritmos de compresión diferentes es determinar cuál comprime más para mi caso de uso. En mi portátil tengo potencia de cálculo de sobra, pero ando escaso de RAM, así que prefiero utilizar el algoritmo que comprima más, aunque requiera más potencia.

Tras usar el portátil unas horas, la cosa queda así:

jcea@jcea:~$ free
           total        used        free      shared  buff/cache   available
Mem:       14328500    13315132      597008       71492      416360      653376
Swap:      16777200     8475540     8301660

jcea@jcea:~$ swapon
NAME       TYPE      SIZE USED PRIO
/dev/zram0 partition   4G   2G    5
/dev/zram1 partition   4G   2G    5
/dev/zram2 partition   4G   2G    5
/dev/zram3 partition   4G   2G    5

jcea@jcea:~$ zramctl
NAME       ALGORITHM DISKSIZE DATA  COMPR  TOTAL STREAMS MOUNTPOINT
/dev/zram3 lzo-rle         4G   2G 645,1M 670,3M       8 [SWAP]
/dev/zram2 zstd            4G   2G 474,7M 493,6M       8 [SWAP]
/dev/zram1 lz4hc           4G   2G 612,4M 638,4M       8 [SWAP]
/dev/zram0 lz4             4G   2G 670,6M   700M       8 [SWAP]

Aquí vemos que estamos usando unos ocho gigabytes de espacio de intercambio y que esa carga se reparte equitativamente entre los cuatro dispositivos ZRAM que estamos utilizando. Bien hasta aquí.

El último comando, zramctl [2], es interesante. Nos muestra los cuatro dispositivos ZRAM, cuántos datos gestiona cada uno y cuánto ocupan realmente en memoria tras la compresión [3].

Por ejemplo, aquí vemos que hemos entregado a ZRAM ocho gigabytes de datos, pero que se almacenan en solo 2.5 gigabytes. A todos los efectos, en este momento, es como si el ordenador tuviese 5.5 gigabytes de RAM adicionales.

El rendimiento será bueno mientras el tamaño de nuestro working set no supere el tamaño de la memoria RAM realmente disponible.

Si hablamos solo de capacidad de compresión, podemos ver que el algoritmo zstd es bastante más potente que el resto. Una vez que validamos esa información con múltiples pruebas, podemos usar un solo dispositivo ZRAM de 16 gigabytes con compresión zstd.

Cada algoritmo tiene también un consumo de CPU diferente que habría que evaluar, pero ese factor no es importante en mi caso. Tengo potencia de sobra, pero ando escaso de RAM.

Según lo visto, el nivel de compresión de cada algoritmo es el siguiente:

lz4 2.93
lzo-rle 3.06
lz4hc 3.21
zstd 4.15
[3] (1, 2, 3, 4, 5) El número de streams es el número de compresiones en paralelo que se pueden hacer en ese dispositivo ZRAM. Originariamente el valor por defecto era uno, pero en versiones actuales de ZRAM el valor por defecto es igual al número de CPUs del ordenador.

Revisión de los algoritmos de compresión disponibles

El número de algoritmos de compresión disponibles va aumentando con el paso del tiempo así que sugiero ir haciendo pruebas cuando actualizamos el sistema operativo.

Para ver los algoritmos disponibles, podemos hacer algo como:

jcea@jcea:~/hg/blog/posts$ cat /sys/block/zram0/comp_algorithm
lzo lzo-rle [lz4] lz4hc 842 zstd

Aquí aparece qué algoritmos hay disponibles y cuál tenemos activado en este momento en el dispositivo ZRAM. Obsérvese que tengo disponibles seis algoritmos de compresión, pero solo estoy comparando cuatro. Esto es así porque el sistema operativo en el que empecé estas pruebas solo tenía esos cuatro algoritmos disponibles.

Como puse más arriba, no se puede cambiar el número de dispositivos ZRAM sin destruirlos todos primero. No puedo permitirme "traer de vuelta" del espacio de intercambio esos ocho gigabytes para añadir dos dispositivos ZRAM nuevos. Así que hago lo siguiente:

  1. Cierro un par de programas grandes, los que ocupan la mayor parte de mi RAM.

  2. Ya con RAM disponible y con buena parte del espacio de intercambio liberado, elimino el espacio de intercambio

    root@jcea:~# swapoff /dev/zram0
    root@jcea:~# swapoff /dev/zram1
    root@jcea:~# swapoff /dev/zram2
    root@jcea:~# swapoff /dev/zram3
    
  3. Con todos los dispositivos ZRAM liberados, elimino el módulo del sistema operativo. El servicio ZRAM desaparece:

    root@jcea:~# rmmod zram
    
  4. Ahora procedemos a cargar el módulo ZRAM de nuevo, esta vez con seis dispositivos. Configuramos cada dispositivo ZRAM con un algoritmo de compresion diferente. Los pasos son los explicados más arriba. El resultado final es:

    root@jcea:~# zramctl
    NAME       ALGORITHM DISKSIZE  DATA COMPR TOTAL STREAMS MOUNTPOINT
    /dev/zram5 842             4G   45M 14,5M 15,5M       8 [SWAP]
    /dev/zram4 lzo             4G 45,3M 13,3M 14,2M       8 [SWAP]
    /dev/zram3 lzo-rle         4G 45,2M 13,6M 14,5M       8 [SWAP]
    /dev/zram2 zstd            4G 44,9M  9,5M 10,2M       8 [SWAP]
    /dev/zram1 lz4hc           4G 45,3M   12M 12,8M       8 [SWAP]
    /dev/zram0 lz4             4G 45,4M 13,4M 14,2M       8 [SWAP]
    
  5. Ya solo queda hacer un uso normal del ordenador y esperar a que el espacio de intercambio se empiece a llenar. Esperaré a que cada ZRAM tenga algo menos de un gigabyte ocupado, para tener mayor precisión a la hora de comparar datos.

    Con mi perfil de uso y demanda de recursos, esto es rápido. Unos minutos.

    El resultado es el siguiente:

    NAME       ALGORITHM DISKSIZE   DATA  COMPR  TOTAL STREAMS MOUNTPOINT
    /dev/zram5 842             4G 971,4M   368M 387,3M       8 [SWAP]
    /dev/zram4 lzo             4G 971,5M 319,5M 334,2M       8 [SWAP]
    /dev/zram3 lzo-rle         4G 971,5M 320,9M 336,4M       8 [SWAP]
    /dev/zram2 zstd            4G 973,3M 230,7M 242,1M       8 [SWAP]
    /dev/zram1 lz4hc           4G 971,4M   301M 316,5M       8 [SWAP]
    /dev/zram0 lz4             4G 971,7M 334,1M 350,6M       8 [SWAP]
    

Según esto, el nivel de compresión de cada método es:

842 2.51
lz4 2.77
lzo-rle 2.89
lzo 2.91
lz4hc 3.07
zstd 4.02

Como vemos, la compresión es un poco inferior a la que vimos antes (depende del tipo de datos que se manden al espacio de intercambio y eso depende de qué estemos haciendo exactamente con el ordenador), pero zstd arrasa completamente.

Por tanto, voy a reconfigurar mis ZRAM para espacio de intercambio con zstd. Aprovecharé también para configurar los streams [3] y que me deje libre algo de CPU en momentos de stress de RAM baja. En principio podría usar un único ZRAM como espacio de intercambio, pero seguiré usando cuatro para facilitar pruebas y migraciones futuras.

Hago lo siguiente [2]:

  1. Retiro los dos últimos dispositivos ZRAM del espacio de intercambio y los reseteo para que estén disponibles si los necesito en el futuro. Lo que almacenan pasa a estar en RAM y si vamos apretados de memoria, esos datos se enviarán inmediatamente al resto de dispositivos ZRAM que quedan en el espacio de intercambio:

    for i in 4 5
    do
        swapoff /dev/zram$i
        zramctl -r /dev/zram$i
    done
    
  2. Hago lo siguiente para cada uno de los cuatro primeros dispositivos ZRAM: retiro el dispositivo del espacio de intercambio, lo reconfiguro para que use compresión zstd y solo un par de CPUs y lo vuelvo a añadir al espacio de intercambio del sistema operativo. El código es el siguiente:

    for i in 0 1 2 3
    do
        swapoff /dev/zram$i
        zramctl /dev/zram$i -s 4G -t 2 -a zstd
        mkswap /dev/zram$i
        swapon -p 5 /dev/zram$i
    done
    

El resultado final es el siguiente:

root@jcea:~# zramctl ; swapon
NAME       ALGORITHM DISKSIZE   DATA  COMPR  TOTAL STREAMS MOUNTPOINT
/dev/zram3 zstd            4G 684,6M 183,4M 189,8M       8 [SWAP]
/dev/zram2 zstd            4G 742,7M 195,2M 202,3M       8 [SWAP]
/dev/zram1 zstd            4G 869,2M 225,1M 233,2M       8 [SWAP]
/dev/zram0 zstd            4G 893,5M   236M 244,4M       8 [SWAP]
NAME       TYPE      SIZE   USED PRIO
/dev/zram0 partition   4G 893,7M    5
/dev/zram1 partition   4G 869,4M    5
/dev/zram2 partition   4G 742,7M    5
/dev/zram3 partition   4G 685,1M    5

La ocupación de los distintos dispositivos ZRAM no es idéntica porque en el proceso los hemos ido metiendo y sacando del espacio de intercambio y, cada vez que quitábamos uno, sus datos acababan parte en RAM y parte en el resto de dispositivos ZRAM. Esto no es ningún problema en absoluto y, en todo caso, con el posterior uso de la máquina las diferencias se van aplanando.

¿Cuándo utilizar ZRAM como espacio de intercambio?

Soy un gran fan del uso del espacio de intercambio o swap, sobre todo en ordenadores con muchos programas cargados, pero con un working set total más pequeño que la RAM disponible. Normalmente el swap debería almacenarse en el disco duro, pero hay varios casos de interés:

  1. Máquinas con abundante RAM, pero sin sistema de almacenamiento masivo (o que no quiere tocarlo, como ocurre con un Live CD). El sistema operativo se ejecuta desde RAM o desde un sistema de almacenamiento de solo lectura. Queremos proporcionar la apariencia de tener más RAM que la real, pero no tenemos a dónde enviar los datos que hay que conservar, pero que hace tiempo que no se usan. Podemos usar ZRAM como espacio de intercambio para esto.

  2. Máquinas cuyo sistema de almacenamiento masivo es del tipo SSD, NVME, MicroSD u otras tecnologías basadas en memoria Flash. Estas memorias se degradan con el uso, especialmente con las escrituras. Usarlas como espacio de intercambio, a poco que el sistema operativo lo necesite, las destruye en poco tiempo. Si la necesidad de espacio de intercambio es modesta, se puede usar una ZRAM para evitar escribir en la memoria flash, evitando así acortar su vida innecesariamente.

    Por ejemplo, una Raspberry PI.

Otros usos comunes de ZRAM

Además de como espacio de intercambio, un dispositivo ZRAM es un dispositivo de bloques que se puede usar para cualquier otra cosa que emplee dispositivos de bloques. Por ejemplo, se puede utilizar como disco duro virtual y montarlo en /tmp/, pongamos por caso, para que la escritura en el directorio temporal tenga un tamaño acotado y, además, no llegue a tocar nunca el almacenamiento masivo del ordenador. Esto puede ser útil, por ejemplo, para el directorio de trabajo intermedio de un sistema de compilación masiva.

Otro uso interesante es crear un dispositivo ZRAM pequeño para almacenar datos delicados que no queremos que se almacenen de forma accidental en un soporte permanente. Por ejemplo, podemos crear un ZRAM de solo unos pocos megabytes, formatearlo y montarlo en /claves/ para almacenar claves de cifrado, etc. Los datos estarían ahí para el sistema, pero sabemos que no se van a almacenar en el disco duro bajo ninguna circunstancia, porque esa memoria no se manda nunca al espacio de intercambio que pudiera residir en el disco duro.

Erratas

  • En varias partes del artículo comento que el número de dispositivos ZRAM se fija a la hora de importar el módulo ZRAM en el sistema operativo. En realidad no es así. Especificar ese parámetro crea los directorios /sys/block/zram* y los dispositivos ZRAM se pueden configurar escribiendo en esos ficheros. No obstante, podemos utilizar el comando zramctl -f para crear y destruir dispositivos ZRAM dinámicamente.
  • En mi sistema operativo intentar configurar el número de streams [3] no parece tener efecto. Siempre se configura ocho streams [3], que es el número de CPUs que tiene mi portátil.