Compactação de memória com o zswap

A memória swap é um elemento importante do gerenciamento de memória no Linux, mas sua utilização, quando notada, geralmente é acompanhada de uma penalidade considerável no desempenho, e por vezes é necessário reiniciar todo o sistema. Por esse e outros motivos, alguns administradores de sistemas não ortodoxos questionam sobre a real necessidade de um dispositivo swap.

Poucas alternativas de configuração são populares: desabilitar totalmente o swap e deixar que o kernel sacrifique alguns processos em uma eventual falta de memória (OOM Killer), ou simplesmente utilizar um disco mais rápido para o dispositivo de swap, como, por exemplo, discos SSD.

Agora disponível no Red Hat Enterprise Linux 7 (e clones), o zswap aparece como uma alternativa sensata e econômica que promete dar novo estímulo ao uso da memória swap.

Em artigo anterior, exploramos a relação entre o swap e o cache do sistema de arquivos. Esclarecemos que, mesmo durante o funcionamento normal do sistema, a memória computacional menos frequentemente acessada é movimentada para a memória swap, cedendo seu valioso espaço para o cache. Ao longo do tempo, dependendo do nível de utilização de memória do sistema, uma quantidade significativa de memória swap é utilizada sem prejuízo aparente no desempenho, pois elas são movimentadas em pequenas porções.

Portanto, o swap tem sua devida importância para o funcionamento do sistema, apesar das considerações sobre seu desempenho. Contudo, se ao invés de enviarmos os dados da nossa rápida memória RAM para o nosso disco extremamente lento (quando comparados), pudéssemos antes compactá-los para ocupar menos espaço na própria memória RAM?

Este é exatamente o conceito do zswap, que pela documentação é definido como um cache compactado para a memória swap. Ele usa ciclos de CPU para compactar a memória, reduzindo ou eliminando a necessidade de I/O para o dispositivo de swap. Esta troca de I/O por CPU pode-se mostrar bastante benéfica em relação ao desempenho do sistema se a leitura dos dados compactados no cache for mais rápida que a leitura do dispositivo swap.

Alguns dos cenários particularmente beneficiados por esse recurso são:

  • Usuários de desktop com uma quantidade limitada de memória RAM podem usar a memória swap sem grande prejuízo no desempenho.
  • Hypervisors com várias máquinas virtuais que compartilham um dispositivo de I/O podem reduzir o impacto no desempenho quando for necessário utilizar o swap para atender as demandas de memória virtual. O zswap estende, portanto, a capacidade de overcommit de memória.
  • Sistemas que utilizam discos SSD podem aumentar a vida útil destes dispositivos ao usar o zswap para reduzir a quantidade de operações de I/O de escrita.

O zswap é integrado ao subsistema de gerenciamento de memória do Linux através da API fornecida pelo frontswap – um mecanismo do kernel que abstrai os vários tipos de dispositivos de storage que podem ser usados como swap. Isto lhe permite interceptar as páginas de memória enquanto elas estão sendo enviadas para o dispositivo swap (swap out) ou interceptar os page faults para as páginas já presentes na memória swap que precisam voltar para a memória RAM (swap in). Na operação de swap out, o zswap compacta a memória e envia para uma área reservada da própria memória RAM. No swap in, ele simplesmente descompacta e volta os dados originais para a memória RAM.

Quando a área reservada na memória RAM para o zswap fica cheia, as páginas mais antigas (Last Recently Used) são movidas para o dispositivo de swap. Esta abordagem faz do zswap um verdadeiro cache para o dispositivo swap, uma vez que apenas as páginas mais recentes são mantidas na área reservada.

Internamente, o zswap faz a compactação das páginas usando os módulos da API crypto (responsável também pelas operações de criptografia do kernel). É possível definir o módulo de compactação a ser usado modificando o parâmetro do kernel zswap.compressor durante o processo de boot. O valor padrão é deflate, que indica o uso do LZO (Lempel–Ziv–Oberhumer). Outro parâmetro de kernel importante é o max_pool_percent, utilizado para indicar o percentual máximo de memória RAM a ser reservado para as páginas compactadas do zswap.

Mão na massa

Vejamos na prática como utilizar o zswap em um sistema CentOS 7. O objetivo é realizarmos testes básicos para compreendermos seu funcionamento e analisarmos seu desempenho.

Para os testes, iremos usar o pequeno programa em python a seguir que chamamos de memalloc.py. Ele será usado para fazermos a alocação de memória e aceita o argumento “-i” para que não encerre após alocá-la.

#!/usr/bin/env python

import sys
import time

def alloc(count,infinite):
   megabyte = (0,) * (1024 * 1024 / 8)
   data = megabyte * count
   if infinite:
      while True:
         time.sleep(1)

if len(sys.argv) < 2 :
    print "usage: fillmem [-i] <number-of-megabytes>"
    sys.exit()

if sys.argv[1] == "-i":
    alloc(int(sys.argv[2]),1)
else:
    alloc(int(sys.argv[1]),0)

O primeiro teste será realizado com o zswap desabilitado e um dispositivo de swap comum, usando uma máquina virtual com 1 GB de memória e 1 CPU virtual.

Antes de executarmos o programa de alocação, esvaziamos o cache do sistema de arquivos, configuramos o parâmetro swappiness para forçarmos o uso do swap ao invés do cache e reiniciamos os dispositivos de swap para garantirmos que eles estejam vazios.

# echo 3 > /proc/sys/vm/drop_caches
# echo 100 > /proc/sys/vm/swappiness
# swapoff -a; swapon -a

Neste momento, o sistema está com 803 Megabytes de memória livre  (sem considerar o cache) e o dispositivo de swap vazio.

# free -m
total used free shared buffers cached
Mem: 987 183 803 6 0 22
-/+ buffers/cache: 161 826
Swap: 2047 0 2047

Vamos criar um processo para alocar 300 Megabytes de memória de maneira persistente. Isto é necessário pois, neste momento, não existe memória inativa suficiente para ser enviada para o dispositivo swap, o que iria distorcer os resultados do teste.

# ./memalloc.py -i 300 &

Agora o sistema está com 502 Megabytes livres:

# free -m
total used free shared buffers cached
Mem: 987 484 502 6 0 19
-/+ buffers/cache: 465 522
Swap: 2047 0 2047

Por fim, criamos um novo processo para alocar 602 Megabytes (100 Megabytes a mais do que a memória livre disponível) para analisarmos o uso do swap:

# time ./memalloc.py 602

real 0m6.678s
user 0m0.594s
sys 0m2.160s

O resultado acima mostra que o programa demorou 6.6 segundos para alocar a memória utilizando o método de swap convencional. A saída do comando iostat durante a execução do programa mostra que houve atividade de disco e que, em determinado momento, o percentual de uso de CPU para I/O wait ultrapassou 50%.

avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 76.12 23.88 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 25468.66 0.00 101874.63 0 68256

avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 100.00 0.00 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 76915.38 0.00 307661.54 0 39996

avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 80.95 19.05 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 46704.76 0.00 186819.05 0 39232

avg-cpu: %user %nice %system %iowait %steal %idle
0.00 0.00 44.83 55.17 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 28065.52 0.00 112262.07 0 32556

avg-cpu: %user %nice %system %iowait %steal %idle
26.92 0.00 73.08 0.00 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 35273.08 0.00 141092.31 0 36684

avg-cpu: %user %nice %system %iowait %steal %idle
77.78 0.00 22.22 0.00 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 36666.67 0.00 146666.67 0 39600

Agora iremos repetir os testes com o zswap.

A partir do kernel 3.13, o zswap precisa ser habilitado explicitamente alterando o valor do parâmetro zswap.enabled para 1 (esta alteração exige a reinicialização do sistema). É possível confirmar se o zswap está habilitado analisando as mensagens do kernel com o comando dmesg:

# dmesg | grep zswap
[ 0.961691] zswap: loading zswap
[ 0.961696] zswap: using lzo compressor

Novamente limpamos o cache do sistema de arquivos, alteramos o parâmetro do swappiness e reiniciamos os dispositivos swap:

# echo 3 > /proc/sys/vm/drop_caches
# echo 100 > /proc/sys/vm/swappiness
# swapoff -a; swapon -a

Neste momento, o sistema está com 808 Megabytes de memória livre  (sem considerar o cache) e o dispositivo de swap vazio.

free -m
total used free shared buffers cached
Mem: 987 178 808 6 0 24
-/+ buffers/cache: 154 833
Swap: 2047 0 2047

Criamos o processo para alocar 300 Megabytes de memória de maneira persistente.

# ./memalloc.py -i 300 &

Agora o sistema está com 503 Megabytes livres:

# free -m
total used free shared buffers cached
Mem: 987 484 503 6 0 22
-/+ buffers/cache: 461 525
Swap: 2047 0 2047

Executamos o programa para alocar 603 Megabytes de memória (100 Megabytes a mais do que a memória disponível).

# time ./memalloc.py 603

real 0m1.891s
user 0m0.175s
sys 0m1.014s

Sob as mesmas circunstâncias de alocação de memória do teste anterior, o programa levou apenas 1.8 segundos para ser executado. Diferentemente do comportamento com o zswap desabilitado, neste teste o iostat não mostrou atividade de disco relevante e o maior tempo de uso de CPU foi relativo ao sistema (%system), usado para fazer a compactação.

avg-cpu: %user %nice %system %iowait %steal %idle
3.51 0.00 96.49 0.00 0.00 0.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 108.77 0.00 435.09 0 248

avg-cpu: %user %nice %system %iowait %steal %idle
10.00 0.00 3.33 11.67 0.00 75.00

Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn
dm-0 0.00 0.00 0.00 0 0

Após o teste, o comando free mostra normalmente o uso de swap, porém, na realidade estes dados estão compactados na própria memória RAM.

# free -m
total used free shared buffers cached
Mem: 987 335 651 6 0 26
-/+ buffers/cache: 309 678
Swap: 2047 331 1716

Finalmente, podemos obter algumas estatísticas sobre o uso do zswap para confirmarmos que ele está realmente funcionando.

# cd /sys/kernel/debug/zswap
# grep . *
duplicate_entry:0
pool_limit_hit:0
pool_pages:42455
reject_alloc_fail:0
reject_compress_poor:124
reject_kmemcache_fail:0
reject_reclaim_fail:0
stored_pages:84704
written_back_pages:0

Conclusão

Apesar dos critérios utilizados nos testes (pouco científicos, admito) não refletirem um cenário real (a memória alocada pelo programa de teste era bastante compactável quando comparada com a de outros programas), é provável que enxerguemos ganho com o zswap na maioria dos cenários, principalmente se considerarmos que, atualmente, ciclos de CPU são significativamente mais baratos em termos de desempenho do que as operações de I/O.

Por mais que a tecnologia avance, a estratégia de evitar I/O a qualquer custo ainda se mostra bastante eficiente na maioria dos casos.

Referências

Zswap Documentation. Zswap overview. Kernel.org. Acessado em 05-01-2015.
zswap. Zswap. Wikipedia. Acessado em 05-01-2015.

Um comentário sobre “Compactação de memória com o zswap

  1. Tchê, muito bom teu artigo. Estou de férias e sem acesso a um computador, mas assim que puder vou por isto em funcionamento no meu Debian.

    Abraço

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *