Eu executo vários processos que consomem muita CPU em paralelo; eles normalmente usam alguns GB de memória cada. De tempos em tempos, eles também alocam grande quantidade de memória (digamos 150-250 GB). Normalmente, no máximo um dos processos faz isso, então eles cabem na RAM disponível (384GB na minha máquina). No entanto, às vezes acontece que mais deles alocam essa grande quantia ao mesmo tempo e (obviamente) tudo fica mais lento por causa da troca.
Nesses casos, paro todos os processos que consomem muita memória, exceto um, para permitir que ele seja computado de maneira eficaz. Mas leva muito tempo para trocar um processo interrompido, pois isso significa carregar dezenas de gigabytes do disco em padrão de acesso aleatório. Portanto, a questão é: como posso forçar um único processo a carregar sequencialmente todo o núcleo do swap?
Até agora, encontrei apenas uma dica de kernel de troca, que (com a ajuda de cgroups) pode impedir que um processo troque mais, mas não ajuda no desempenho da troca. Obviamente, desligar toda a troca não é possível, pois os outros processos interrompidos precisam ocupar espaço lá.
Construir meu próprio mini-agendador também não é uma opção - os processos são vários pequenos scripts/programas em python e os picos de memória geralmente acontecem em chamadas de biblioteca, portanto, não posso prever quando ocorrerá um pico.
Só para deixar claro: não considero comprar terabytes de RAM, nessa escala é muito caro. Colocar swap no array SSD/SSD ajudará apenas um pouco (medido), portanto também não é uma solução que estou procurando.
(auto-resposta parcial):
Parece que a leitura de swap realmente sequencial (apenas páginas que pertencem a um único processo) dificilmente é possível sem hackear o kernel: eu medi swapoff -a
e certamente não leu o swap sequencialmente. E seria lógico lê-lo mais rápido se tal otimização fosse fácil de implementar.
Atualmente minha melhor abordagem é ler toda a memória do processo através de /proc/[pid]/mem
pseudo-arquivo usando o script abaixo (que deve ser executado como root):
#!/usr/bin/python2
import re
import sys
pid=str(sys.argv[1]) # process pid given by the first arg
print(pid) # just to aviod mistakes
CHUNKSIZE=10485760 # single read() invocation block size
total=0
maps_file = open("/proc/"+pid+"/maps", 'r')
mem_file = open("/proc/"+pid+"/mem", 'r', 0)
for line in maps_file.readlines(): # for each mapped region
m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
if m.group(3) == 'r': # if this is a readable region
start = int(m.group(1), 16)
end = int(m.group(2), 16)
mem_file.seek(start) # seek to region start
togo = end-start # number of bytes to read
while togo > CHUNKSIZE: # read sequential memory from region one block at the moment
mem_file.read(CHUNKSIZE)
togo -= CHUNKSIZE
total += CHUNKSIZE
print(total/1048576) # be verbose, print megabytes read so far
mem_file.read(togo) # read remaining region contents
total+=togo # dump contents to standard output
print(total/1048576) # be verbose...
maps_file.close()
mem_file.close()
Acontece que falha nos últimos bytes de memória, mas geralmente funciona com o mesmo desempenho swapoff
e carrega apenas o processo determinado. O script é um trecho modificado deesta resposta.