Я пытаюсь понять, как делиться объектами только для чтения с многопроцессорностью
Я пытаюсь понять, как делиться объектами только для чтения с многопроцессорностью. разделение bigset
когда это глобальная переменная работает нормально:
from multiprocessing import Pool
bigset = set(xrange(pow(10, 7)))
def worker(x):
return x in bigset
def main():
pool = Pool(5)
print all(pool.imap(worker, xrange(pow(10, 6))))
pool.close()
pool.join()
if __name__ == '__main__':
main()
htop
показывает, что родительский процесс использует 100% ЦП и 0,8% памяти, а рабочая нагрузка равномерно распределяется между пятью дочерними процессами: каждый использует 10% ЦП и 0,8% памяти. Все хорошо.
Но цифры начинают сходить с ума, если я перееду bigset
внутри main
:
from multiprocessing import Pool
from functools import partial
def worker(x, l):
return x in l
def main():
bigset = set(xrange(pow(10, 7)))
_worker = partial(worker, l=bigset)
pool = Pool(5)
print all(pool.imap(_worker, xrange(pow(10, 6))))
pool.close()
pool.join()
if __name__ == '__main__':
main()
Сейчас htop
2 или 3 процесса показывают скачок вверх и вниз между 50% и 80% ЦП, в то время как остальные процессы используют менее 10% ЦП. И хотя родительский процесс все еще использует 0,8% памяти, теперь все дети используют 1,9% памяти.
Что происходит?
1 ответ
Когда вы проходите bigset
в качестве аргумента он выбирается родительским процессом и не выбирается дочерними процессами.[1] [2]
Соление и расслоение большого набора требует много времени. Это объясняет, почему вы видите несколько процессов, выполняющих свою работу: родительский процесс должен перебирать много больших объектов, а дети должны ждать его. Родительский процесс является узким местом.
Параметры травления подразумевают, что параметры должны быть отправлены процессам. Для отправки данных из процесса в другой требуются системные вызовы, поэтому вы не видите 100% загрузки ЦП кодом пользовательского пространства. Часть процессорного времени тратится на пространство ядра.[3]
Выборка объектов и отправка их в подпроцессы также подразумевает, что: 1. вам нужна память для буфера выбора; 2. каждый подпроцесс получает копию bigset
, Вот почему вы видите увеличение использования памяти.
Вместо этого, когда bigset
является глобальной переменной, она никуда не отправляется (если вы не используете метод запуска, отличный от fork). Он просто наследуется подпроцессами как есть, используя обычные правила копирования при записи: fork()
,
Примечания:
Если вы не знаете, что означает "выборка": pickle - это один из стандартных протоколов Python для преобразования произвольных объектов Python в последовательность байтов и из нее.
imap()
& co. используйте очереди за кулисами, и очереди работают, выбирая / убирая объекты.Я попытался запустить ваш код (с
all(pool.imap(_worker, xrange(100)))
для того, чтобы сделать процесс быстрее) и я получил: 2 минуты пользовательского времени, 13 секунд системного времени. Это почти 10% системного времени.