Я запускаю несколько ресурсоемких процессов параллельно; обычно они используют несколько ГБ памяти каждый. Время от времени они также выделяют большой объем памяти (скажем, 150-250 ГБ). Обычно это делает максимум один из процессов, чтобы они уместились в доступной оперативной памяти (384 ГБ на моей машине). Однако иногда случается, что несколько из них выделяют этот большой объем одновременно, и (очевидно) все замедляется из-за подкачки.
В таких случаях я останавливаю все, кроме одного, из процессов, поглощающих память, чтобы позволить ему эффективно вычислять. Но на подкачку остановленного процесса уходит уйма времени, поскольку это означает загрузку десятков гигабайт с диска в случайном порядке. Отсюда вопрос: как заставить один процесс последовательно загрузить все ядро из подкачки?
Пока что я нашел только подсказку ядра swappiness, которая (с помощью cgroups) может помешать процессу больше подкачивать, но не помогает производительности де-свопинга. Отключить весь своп, очевидно, невозможно, поскольку другие, остановленные процессы должны занимать там место.
Создание собственного мини-планировщика также не является вариантом — процессы представляют собой различные небольшие скрипты/программы на Python, а пики памяти обычно возникают при вызовах библиотек, поэтому я не могу предсказать, когда произойдет пик.
Просто для ясности: я не рассматриваю покупку терабайт оперативной памяти, в таком масштабе это слишком дорого. Размещение подкачки на массиве SSD/SSD поможет лишь немного (измерено), так что это также не то решение, которое я ищу.
(частичный ответ):
Похоже, что действительно последовательное чтение свопа (только страницы, принадлежащие одному процессу) вряд ли возможно без взлома ядра: я измерил, swapoff -a
и оно определенно не читало своп последовательно. И было бы логично читать его быстрее, если бы такую оптимизацию было легко реализовать.
В настоящее время лучшим подходом является чтение всей памяти процесса через /proc/[pid]/mem
псевдофайл с использованием скрипта ниже (который необходимо запустить от имени пользователя 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()
Он иногда дает сбой на самых последних байтах памяти, но в целом работает с той же производительностью, что swapoff
и загружает только данный процесс. Скрипт — это измененный фрагмент изэтот ответ.