Подтвердить что ты не робот

ProcessPoolExecutor от concurrent.futures намного медленнее, чем многопроцессор.

Я экспериментировал с новым блестящим concurrent.futures модулем, представленным в Python 3.2, и я заметил, что почти с идентичным кодом использование пула из параллельного .futures медленнее, чем использование multiprocessing.Pool.

Это версия с использованием многопроцессорности:

def hard_work(n):
    # Real hard work here
    pass

if __name__ == '__main__':
    from multiprocessing import Pool, cpu_count

    try:
        workers = cpu_count()
    except NotImplementedError:
        workers = 1
    pool = Pool(processes=workers)
    result = pool.map(hard_work, range(100, 1000000))

И это использует concurrent.futures:

def hard_work(n):
    # Real hard work here
    pass

if __name__ == '__main__':
    from concurrent.futures import ProcessPoolExecutor, wait
    from multiprocessing import cpu_count
    try:
        workers = cpu_count()
    except NotImplementedError:
        workers = 1
    pool = ProcessPoolExecutor(max_workers=workers)
    result = pool.map(hard_work, range(100, 1000000))

Используя наивную функцию факторизации, взятую из этой статьи Эли Бендерски, это результаты на моем компьютере (i7, 64-bit, Arch Linux):

[[email protected]]─[~/Development/Python/test]
└[10:31:10] $ time python pool_multiprocessing.py 

real    0m10.330s
user    1m13.430s
sys 0m0.260s
[[email protected]]─[~/Development/Python/test]
└[10:31:29] $ time python pool_futures.py 

real    4m3.939s
user    6m33.297s
sys 0m54.853s

Я не могу профилировать их с помощью профилировщика Python, потому что я получаю ошибки рассола. Любые идеи?

4b9b3361

Ответ 1

При использовании map от concurrent.futures каждый элемент из итеративного отправляется отдельно исполнителю, который создает объект Future для каждого вызова. Затем он возвращает итератор, который дает результаты, возвращаемые фьючерсами.
Future объекты довольно тяжеловесны, они выполняют большую работу, чтобы разрешить все функции, которые они предоставляют (например, обратные вызовы, возможность отмены, проверка состояния,...).

По сравнению с этим multiprocessing.Pool имеет гораздо меньше накладных расходов. Он отправляет задания партиями (сокращая расходы на IPC) и напрямую использует результат, возвращаемый функцией. Для больших партий заданий многопроцессорность, безусловно, лучшие варианты.

Фьючерсы прекрасны, если вы хотите суметь долго работающие задания, где накладные расходы не так важны, когда вы хотите получать уведомление по обратному сообщению или время от времени проверять, выполнены ли они или отменить выполнение индивидуально.

Личное примечание:

Я не могу придумать много причин использовать Executor.map - он не дает вам никаких функций фьючерсов - кроме возможности указать тайм-аут. Если вас просто интересуют результаты, вам лучше использовать одну из функций карты multiprocessing.Pool.