Portanto, hoje faremos .. Um minúsculo trabalho de laboratório. Na forma de um pequeno programa C que escrevemos, compilamos e testamos em ação - com e sem swap.
O programa faz uma coisa muito simples - solicita uma grande quantidade de memória, acessa-a e trabalha ativamente com ela. Para não sofrer com o carregamento de nenhuma biblioteca, vamos simplesmente criar um grande arquivo que será mapeado na memória da mesma forma que o sistema faz ao carregar bibliotecas compartilhadas.
E simplesmente emulamos a chamada do código desta "biblioteca" lendo esse arquivo mmap.
O programa fará várias iterações, a cada iteração acessará simultaneamente o “código” e uma das seções de um grande segmento de dados.
E, para não escrever código desnecessário, definiremos duas constantes que determinarão o tamanho do "segmento de código" e o tamanho total da RAM:
- MEM_GBYTES - o tamanho da RAM para o teste
- LIB_GBYTES - tamanho do "código"
A quantidade de "dados" que temos é menor que a quantidade de memória física:
- DATA_GBYTES = MEM_GBYTES - 2
A quantidade total de "código" e "dados" é ligeiramente maior do que a quantidade de memória física:
- DATA_GBYTES + LIB_GBYTES = MEM_GBYTES + 1
Para um teste em um laptop, peguei MEM_GBYTES = 16 e obtive as seguintes características:
- MEM_GBYTES = 16
- DATA_GBYTES = 14 - significa que "dados" serão 14 GB, ou seja, "memória suficiente"
- Tamanho de troca = 16 GB
Texto do 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;
}
Teste sem usar troca
Desative a troca especificando vm.swappines = 0 e execute o teste
$ time ./swapdemo Preparing test ... Killed real 0m6,279s user 0m0,459s sys 0m5,791s
O que aconteceu? O valor de troca = 0 desabilitou a troca - páginas anônimas não são mais inseridas nela, ou seja, os dados estão sempre na memória. O problema é que os 2 GB restantes não foram suficientes para o Chrome e o VSCode rodarem em segundo plano, e o OOM-killer matou o programa de teste. E, ao mesmo tempo, a falta de memória enterrou a guia do Chrome em que escrevi este artigo. E eu não gostei - mesmo se o salvamento automático funcionou. Não gosto quando meus dados são enterrados.
Troca incluída
Defina vm_swappines = 60 (padrão)
Execute o teste:
$ 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 do 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 ruim, ruim !!! Ele usa quase 4 gigabytes de swap, embora tenha 14 gigabytes de cache e 3 gigabytes disponíveis! Linux tem configurações erradas! Outlingo ruim, administradores antigos ruins, eles não entendem nada, disseram para habilitar a troca e agora fazem a troca do sistema e funcionam mal para mim. É necessário desabilitar a troca conforme aconselhado por especialistas em Internet muito mais jovens e promissores, porque eles sabem exatamente o que fazer!
Bem ... assim seja. Vamos desligar a troca o máximo possível seguindo os conselhos dos especialistas?
Teste quase sem troca
Definimos vm_swappines = 1
Este valor levará ao fato de que a troca de páginas anônimas será realizada somente se não houver outra saída.
Confio em Chris Down porque acho ele um grande engenheiro e sabe o que diz quando explica que o arquivo de troca melhora o desempenho do sistema. Portanto, esperando que “algo” “desse errado” e o sistema funcionasse de maneira terrivelmente ineficiente, me certifiquei com antecedência e executei o programa de teste, limitando-o com um temporizador para ver pelo menos seu encerramento anormal.
Vejamos primeiro o resultado principal:
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
Hooray ?! A troca é usada apenas para 2,5 gigabytes, o que é quase 2 vezes menos do que no teste com a troca habilitada (e troca = 60). A troca é menos usada. Também há menos memória livre. E provavelmente podemos dar a vitória com segurança aos jovens especialistas. Mas aqui está a coisa estranha - nosso programa nunca foi capaz de completar 1 (UMA!) Iteração em 2 (DOIS!) 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 - o programa não conseguiu completar 1 iteração em 2 minutos, embora no teste anterior tenha feito 11 iterações em 2 minutos - ou seja, com a troca quase desabilitada, o programa roda mais de 10 (!) Vezes mais lento.
Mas há uma vantagem - nem uma única guia do Chrome foi danificada. E isso é bom.
Teste com a desativação completa da troca
Mas talvez apenas "esmagar" a troca por meio de troca não seja suficiente, e ela deve ser completamente desativada? Naturalmente, essa teoria também deve ser testada. Viemos aqui para fazer testes ou o quê?
Este é o caso ideal:
- não temos swap e todos os nossos dados serão garantidos na memória
- a troca não será usada nem mesmo acidentalmente, porque não está lá
E agora nosso teste vai terminar na velocidade da luz, os velhos vão para o lugar que merecem e vão trocar os cartuchos - o caminho dos jovens.
Infelizmente, o resultado da execução do programa de teste é semelhante - nem mesmo uma iteração foi concluída.
Resultado principal:
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 que isso está acontecendo
A explicação é muito simples - o “segmento de código” que conectamos via mmap (libptr) está no cache. Portanto, quando proibimos (ou quase proibimos) a troca de uma forma ou de outra, não importa como - desativando fisicamente a troca ou por meio de vm.swappines = 0 | 1 - sempre termina com o mesmo cenário - liberando o arquivo mmap do cache e depois carregá-lo do disco. E as bibliotecas são carregadas exatamente por meio do mmap e, para verificar isso, você só precisa fazer 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
E, como consideramos na primeira parte do artigo, o sistema, em condições de real falta de memória, quando a troca de páginas anônimas estiver desabilitada, irá escolher a única opção que foi deixada pelo proprietário que desabilitou a troca. E esta opção está recuperando (liberando) páginas em branco ocupadas pelos dados de bibliotecas carregadas por mmap.
Conclusão
O uso ativo do método de distribuição de software “Eu levo tudo comigo” (flatpak, snap, imagem docker) leva ao fato de que a quantidade de código conectado via mmap aumenta significativamente.
Isso pode levar ao fato de que o uso de "otimizações extremas" associadas à configuração / desativação da troca pode levar a efeitos completamente inesperados, porque um arquivo de troca é um mecanismo para otimizar o subsistema de memória virtual sob condições de pressão de memória, e a memória disponível é completamente não "memória não utilizada", mas a soma do cache e da memória livre.
Ao desativar o arquivo de troca, você não "remove a opção errada", mas "não deixa opções"
Você deve ter muito cuidado ao interpretar os dados de consumo de memória do processo - VSS e RSS. Eles representam o "estado atual" e não o "estado ideal".
Se você não quiser que o sistema use a troca, adicione memória a ele, mas não desative a troca . Desativar a troca em níveis de limite tornará a situação muito pior do que teria sido se o sistema tivesse sido trocado um pouco.
PS: Nas discussões, perguntas são feitas regularmente "mas se você ativar a compactação de memória via zram ...". Fiquei curioso e executei os testes apropriados: se você habilitar zram e trocar, como é feito por padrão no Fedora, o tempo de execução acelera para cerca de 1 minuto.
Mas a razão para isso é que as páginas com zeros são muito bem compactadas, então, na verdade, os dados não vão para a troca, mas são armazenados em uma forma compactada na RAM. Se você preencher um segmento de dados com dados aleatórios e pouco compactáveis, a imagem se tornará menos espetacular e o tempo de execução do teste aumentará novamente para 2 minutos, o que é comparável (e até um pouco pior) do que um arquivo de troca "honesto".