/proc/sys/vm/swappiness

Existe muito mito e informação errada sobre o parâmetro swappiness do Linux. Afinal, do que ele realmente se trata?

A resposta curta: swappiness é um valor, cujo padrão é 60, usado para calcular a tendência do sistema operacional de mover dados para a memória swap em vez de usar o filesystem cache nas situações de falta de memória RAM.

Agora, a resposta completa.

Swap faz parte do conceito de memória virtual. Trata-se de uma partição no disco rígido usada para estender a memória RAM, permitindo os programas alocarem mais memória computacional (aquela que o processo usa para somar x+y) do que a quantidade de memória RAM instalada no sistema. Como o desempenho dos discos rígidos é muito inferior ao da memória RAM, o sistema de gerenciamento de memória transfere dados para a partição de swap somente quando não há mais memória RAM disponível.

Filesystem cache, como o próprio nome revela, é um recurso do sistema de gerenciamento de memória que armazena na memória RAM os dados dos arquivos que os processos acessam. O Linux, sempre que possível, utiliza a memória RAM disponível para filesystem cache. O objetivo: melhorar o desempenho de acesso aos arquivos, visto que, se mantidos na memória RAM, na próxima vez em que os processos acessá-los, não precisarão buscá-los no disco rígido.

Antes de continuarmos, vamos abrir um parênteses para analisarmos o comportamento e os efeitos do filesystem cache.

Primeiramente, em um diretório /tmp/teste, criamos dois arquivos de 256 Megabytes.

# mkdir /tmp/teste
# cd /tmp/teste
# dd if=/dev/zero of=arquivo1 bs=1M count=256
256+0 records in
256+0 records out
268435456 bytes (268 MB) copied, 0.35711 s, 752 MB/s
# dd if=/dev/zero of=arquivo2 bs=1M count=256
256+0 records in
256+0 records out
268435456 bytes (268 MB) copied, 0.299388 s, 897 MB/s

Para garantir, esvaziamos o filesystem cache:

# echo 3 > /proc/sys/vm/drop_caches

A coluna cached do comando free mostra que apenas 5 Megabytes estão no filesystem cache.

# free -m
total used free shared buffers cached
Mem: 996 59 936 0 0 5
-/+ buffers/cache: 54 942
Swap: 2047 0 2047

Vamos ler os dois arquivos de 256 Megabytes para verificarmos o tempo.

# time cat arquivo* > /dev/null

real 0m1.300s
user 0m0.008s
sys 0m0.336s

A leitura dos dois arquivos demorou 1.3 segundos.

Os dados foram armazenados no cache, conforme o resultado a seguir do comando free. Agora a coluna cached contém os 5 Megabytes anteriores mais os 512 Megabytes dos dois arquivos recém-lidos.

# free -m
total used free shared buffers cached
Mem: 996 572 424 0 0 517
-/+ buffers/cache: 54 941
Swap: 2047 0 2047

Durante a leitura dos arquivos, o comando vmstat mostrou atividade de leitura do disco rígido.

# vmstat 1
procs ———–memory———- —swap– —–io—- –system– —–cpu—–
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 951716 3660 8012 0 0 0 0 32 39 0 0 100 0 0
0 0 0 951716 3660 8012 0 0 0 0 30 38 0 0 100 0 0
0 0 0 951740 3660 8012 0 0 0 0 27 37 0 0 100 0 0
0 0 0 951740 3668 8008 0 0 0 24 31 46 0 0 100 0 0
1 0 0 686504 3668 272812 0 0 264944 0 2120 3022 1 1 74 24 0
0 0 0 426476 3668 532344 0 0 259392 0 2093 3000 0 2 75 23 0
0 0 0 426484 3668 532356 0 0 0 0 22 37 0 0 100 0 0

Com os dados agora no cache, vamos repetir o processo de leitura.

# time cat arquivo* > /dev/null

real 0m0.146s
user 0m0.003s
sys 0m0.142s

Apenas 0.1 segundo para ler os mesmos 512 Megabytes.

O vmstat, durante a segunda leitura, mostrou que não houve nenhuma atividade de disco. Portanto, todos os dados vieram da memória RAM.

# vmstat 1
procs ———–memory———- —swap– —–io—- –system– —–cpu—–
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 0 426492 3672 532356 0 0 0 0 29 35 0 0 100 0 0
0 0 0 426484 3672 532356 0 0 0 0 24 35 0 0 100 0 0
0 0 0 426484 3672 532356 0 0 0 0 36 49 0 0 100 0 0
0 0 0 426476 3672 532356 0 0 0 0 188 60 0 6 94 0 0
0 0 0 426484 3672 532356 0 0 0 0 24 37 0 0 100 0 0
0 0 0 426484 3672 532356 0 0 0 0 25 39 0 0 100 0 0
0 0 0 426484 3672 532356 0 0 0 0 23 37 0 0 100 0 0
0 0 0 426484 3672 532356 0 0 0 0 25 37 0 0 100 0 0
0 0 0 426484 3672 532356 0 0 0 0 22 35 0 0 100 0 0

Fechado o parênteses, fica a questão: se o sistema operacional automaticamente utiliza a memória livre para o filesystem cache, o que acontecerá quando ele precisar alocar memória para um novo processo e não houver mais memória RAM disponível?

Como o filesystem cache é um recurso usado apenas para melhorar o desempenho de acesso aos arquivos, nada mais justo do que o sistema roubar algumas páginas de memória desta área para dar ao novo processo. Correto?

Acontece que alguns processos alocam e preenchem um grande segmento de memória computacional sem que haja a necessidade de acessá-los com frequência.

Neste momento, surge o dilema. Melhor o sistema roubar as páginas de memória acessadas recentemente do filesystem cache, correndo o risco de prejudicar o desempenho dos processos que estão fazendo uso delas, ou roubar páginas de memória computacional de um processo preguiçoso que não as acessam com frequência?

E a resposta é justamente o swappiness.

Os desenvolvedores do kernel resolveram criar este parâmetro para definir a tendência do sistema de fazer swap em vez de roubar memória do filesystem cache. No código-fonte do gerenciador de memória (mais especificamente no arquivo linux/mm/vmscan.c) encontramos o seguinte trecho:

684 * `distress’ is a measure of how much trouble we’re having reclaiming
685 * pages. 0 -> no problems. 100 -> great trouble.
686 */
687 distress = 100 >> zone->prev_priority;
688
689 /*
690 * The point of this algorithm is to decide when to start reclaiming
691 * mapped memory instead of just pagecache. Work out how much memory
692 * is mapped.
693 */
694 mapped_ratio = (sc->nr_mapped * 100) / total_memory;
695
696 /*
697 * Now decide how much we really want to unmap some pages. The mapped
698 * ratio is downgraded – just because there’s a lot of mapped memory
699 * doesn’t necessarily mean that page reclaim isn’t succeeding.
700 *
701 * The distress ratio is important – we don’t want to start going oom.
702 *
703 * A 100% value of vm_swappiness overrides this algorithm altogether.
704 */
705 swap_tendency = mapped_ratio / 2 + distress + vm_swappiness;
706
707 /*
708 * Now use this metric to decide whether to start moving mapped memory
709 * onto the inactive list.
710 */
711 if (swap_tendency >= 100)
712 reclaim_mapped = 1;

A fórmula para calcular a tendência de swap é baseada nos valores descritos a seguir:

  • mapped_ratio: a razão entre quantidade de memória computacional mapeada pelos processos e o total de memória do sistema.
  • distress: um valor atribuído de maneira incremental de acordo com as situações em que o sistema encontrou problemas para liberar memória.
  • vm_swappiness: o valor definido pelo parâmetro swappiness.

Quando o resultado da fórmula for igual ou maior que 100, o sistema irá considerar mover páginas de memória computacional não acessadas recentemente para a área de swap.

Vamos imaginar um sistema com 1024 Megabytes de memória RAM. Ele não está encontrando problemas para alocar memória RAM para os processos, o que significa que o valor de distress é 0. A quantidade de páginas de memória computacional mapeadas para os processos ocupa 820 Megabytes, ou seja, 80% do total. O valor de swappiness é o padrão: 60

swap_tendency = 80 / 2 + 0 + 60 = 100

Neste cenário, swap_tendency é igual a 100, sendo assim, caso um novo processo requisite memória computacional sem que haja memória RAM livre o suficiente para atendê-lo, o sistema irá igualar as prioridades do filesystem cache e do swap e, consequentemente, irá mover algumas páginas de memória computacional inativas de outros processos para a área de swap.

Portanto, swappiness não define exatamente o porcentual entre mover memória computacional para a área de swap e roubar memória do filesystem cache. Ele simplesmente é usado para calcular a tendência deste comportamento, mas não a quantidade exata de memória que será movida.

Só acredito vendo…

Considerando o mesmo sistema com 1024 Megabytes de memória RAM, vamos usar um programa para alocar memória computacional e criar um processo para ler arquivos e, dessa maneira, usar o filesystem cache.

Primeiramente, alteramos o parâmetro swappiness para 100. Isso irá garantir que a fórmula de tendência de swap seja maior ou igual a 100.

# echo 100 > /proc/sys/vm/swappiness

Para alocarmos memória computacional, compilamos o programa memalloc.c com o código a seguir:

int main(int argc, char **argv)
{
int i;
for (i = 0; i = atoi(argv[1]); i++) {
void *m = malloc(1024*1024);
memset(m,0,1024*1024);
}
getchar();
return(0);
}

E para usarmos o filesystem cache, criamos 200 arquivos de 2 MB em /tmp/teste:

# mkdir /tmp/teste
# for i in `seq 1 200`; do dd if=/dev/zero of=/tmp/teste/arquivo$i bs=2M count=1; done

É hora de esvaziarmos o filesystem cache e o swap para iniciarmos os testes.

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

Com o comando free, confirmamos que o swap está vazio e que o filesystem cache contém apenas o necessário para o sistema operacional – aproximadamente 3 Megabytes.

# free -m
total used free shared buffers cached
Mem: 996 62 934 0 0 3
-/+ buffers/cache: 58 938
Swap: 2047 0 2047

Em seguida, criamos um processo em loop para ler os arquivos criados anteriormente. Isso irá manter as páginas do filesystem cache ativas.

# while true; do cat /tmp/teste/arquivo* > /dev/null; done

Neste momento o filesystem cache está ocupando aproximadamente 400 Megabytes. 530 Megabytes ainda estão livre.

# free -m
total used free shared buffers cached
Mem: 996 464 532 0 1 404
-/+ buffers/cache: 58 937
Swap: 2047 0 2047

Vamos criar um processo para alocarmos 400 Megabytes de memória computacional.

# ./memalloc 400 &

O comando free mostra agora que apenas 129 Megabytes estão livres.

# free -m
total used free shared buffers cached
Mem: 996 866 129 0 1 404
-/+ buffers/cache: 461 535
Swap: 2047 0 2047

Momento de analisarmos um arquivo importante, o /proc/meminfo. Ele dá informações detalhadas sobre a memória do sistema. Do resultado obtido, separamos apenas o trecho que indica a quantidade de memória computacional ativa (mais recentemente acessada) e inativa (menos recentemente acessada) – respectivamente, Active(anon) e Inactive(anon). Também a quantidade de filesystem cache ativa e inativa.

# cat /proc/meminfo

Active(anon): 413948 kB
Inactive(anon): 8940 kB
Active(file): 412088 kB
Inactive(file): 3128 kB

Lembrando, quando o resultado da fórmula de tendência de swap for igual ou maior que 100 (em nosso caso ele será pois o parâmetro swappiness está em 100), ele irá considerar mover para o swap apenas a memória computacional inativa, que em nosso caso está com aproximadamente 8 Megabytes.

A partir deste momento, como temos apenas 129 Megabytes de memória livre, vamos criar um processo de 200 Megabytes e analisarmos o comportamento do sistema.

# ./memalloc 200

O monitoramento com o vmstat nos mostra que houve atividade de swap out. Aproximadamente 2 Megabytes foram transferidos da memória RAM para a partição de swap.

# vmstat 1

procs ———–memory———- —swap– —–io—- –system– —–cpu—–
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 132652 1520 413868 3 39 13950 56 37 15 0 4 93 3 0
1 0 0 132652 1520 413892 0 0 0 0 1906 131 2 48 50 0 0
1 0 0 132652 1520 413892 0 0 0 0 1988 163 2 49 50 0 0
1 0 0 132652 1520 413892 0 0 0 0 2010 203 2 48 50 0 0
1 0 0 132652 1520 413892 0 0 0 0 1896 129 2 48 50 0 0
1 1 2324 62400 80 280884 0 2324 23360 2324 2610 508 3 52 43 3 0
0 1 2324 67856 80 275376 0 0 384808 0 4325 4486 0 21 47 33 0
1 0 2324 63516 80 279772 0 0 387088 0 4388 4405 0 20 47 32 0
0 1 2324 70832 80 272504 0 0 374160 0 4288 4257 1 20 48 32 0

Finalizamos o último processo do memalloc e, ao analisarmos novamente o arquivo /proc/meminfo, notamos que o gerenciador de memória do sistema considerou a fórmula de tendência de swap e marcou mais memória computacional como inativa, aumentando o campo Inactive(anon) para aproximadamente 290 Megabytes.

Active(anon): 130836 kB
Inactive(anon): 290336 kB
Active(file): 411976 kB
Inactive(file): 1216 kB

E o resultado do comando free confirma o uso de 2 Megabytes de swap.

# free -m
total used free shared buffers cached
Mem: 996 863 133 0 0 403
-/+ buffers/cache: 459 536
Swap: 2047 2 2045

Vamos executar novamente o memalloc para alocar os mesmos 200 Megabytes.

# ./memalloc 200

Como a quantidade de memória computacional inativa estava maior, o vmstat nos mostrou durante a execução do memalloc que o sistema transferiu mais dados para a área de swap.

# vmstat 1

procs ———–memory———- —swap– —–io—- –system– —–cpu—–
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 2196 136356 80 413340 0 0 0 0 1913 161 2 48 50 0 0
1 0 2196 136356 80 413340 0 0 0 0 1962 135 2 48 50 0 0
1 0 2196 136356 80 413340 0 0 0 0 1927 153 2 49 50 0 0
1 0 2196 136356 80 413340 0 0 0 0 1976 144 2 48 50 0 0
1 0 2196 136356 80 413340 0 0 0 0 1987 137 2 48 50 0 0
0 1 51080 70320 80 319552 96 48872 284892 48872 4469 4204 1 33 41 25 0
1 0 51720 63468 80 326992 0 640 353456 640 4698 5345 1 20 48 31 0
0 1 51976 66908 80 323796 0 256 353924 256 4905 5694 0 20 48 32 0
0 1 51976 64180 80 326664 0 0 359732 0 4388 5417 1 18 49 33 0
1 0 51976 60708 80 330028 0 0 348472 0 4808 5620 0 21 48 31 0
1 0 51976 67280 80 323516 0 0 361148 0 4750 5433 1 20 48 32 0

Novamente através do comando free, confirmamos que, no total, 50 Megabytes foram movidos para a partição de swap.

# free -m
total used free shared buffers cached
Mem: 996 930 66 0 0 315
-/+ buffers/cache: 614 381
Swap: 2047 50 1997

Para finalizarmos, esvaziamos o swap, alteramos o parâmetro swappiness para 0 e criamos novamente o processo de 200 Megabytes.

# swapoff -a; swapon -a
# echo 0 > /proc/sys/vm/swappiness
# ./memalloc 200

Podemos observar no monitoramento com o vmstat que desta vez não houve atividade na partição de swap. O sistema roubou a memória necessária para o novo processo apenas do filesystem cache.

# vmstat 1

procs ———–memory———- —swap– —–io—- –system– —–cpu—–
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 130348 2700 414764 0 0 0 0 1929 126 2 49 50 0 0
1 0 0 130348 2700 414764 0 0 0 0 1876 160 2 48 50 0 0
1 0 0 130348 2700 414764 0 0 0 0 2030 173 2 48 50 0 0
0 1 0 70824 208 270556 0 0 113524 0 3197 2050 2 28 42 29 0
0 1 0 64252 208 277184 0 0 191464 0 2481 2814 0 9 50 42 0
1 1 0 62392 208 278992 0 0 348440 0 4284 4864 0 18 48 34 0
2 0 0 62764 208 278632 0 0 345508 0 4418 5442 1 21 47 32 0
0 1 0 62888 208 278616 0 0 369596 0 4443 5048 0 19 48 33 0
0 1 0 61896 208 279412 0 0 359760 0 4457 5071 1 19 48 33 0
0 1 0 66732 208 274712 0 0 353800 0 4559 5495 0 20 48 32 0
0 1 0 66856 208 274524 0 0 369576 0 4480 4954 1 18 48 34 0
0 1 0 66484 208 274816 0 0 357632 0 4426 5060 1 18 48 33 0

O free confirma que a partição de swap está vazia.

# free -m
total used free shared buffers cached
Mem: 996 931 65 0 0 268
-/+ buffers/cache: 662 333
Swap: 2047 0 2047

E daí?

Baseado nesta explicação relativamente simples, muita complexidade está envolvida na definição do valor do swappiness.

Na prática, como um valor baixo reduz o uso de swap, o tempo de inicialização de um processo tende a ser menor, pois não é necessário aguardar que algumas páginas de memória sejam movidas da memória RAM para o disco rígido.

Justamente por melhorar a percepção do tempo de inicialização das aplicações, reduzir o valor do swappiness é uma configuração geralmente recomendada para ambientes de Desktop. Contudo, ele pode comprometer o desempenho das aplicações já em execução, devido à consequente redução do filesystem cache.

Em servidores, a análise da carga de trabalho deve ser mais criteriosa. Existem muitos processos ocupando muitas páginas de memória computacional e as acessando com pouca frequência? O filesystem cache não oferece muito ganho de desempenho? Os processos são interativos ou batch? Estas e outras questões devem ser consideradas e, principalmente, testes devem ser realizados para se obter o melhor valor para o swappiness.

Referências

Corbet (05-05-2004). 2.6 swapping behavior. LWN.net. Acessado em 10-10-2013.
McCandless, Michael (24-04-2011). Just say no to swapping!. blog.mikemccandless.com. Acessado em 11-10-2013.

7 comentários sobre “/proc/sys/vm/swappiness

Deixe um comentário

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