Почему версия `multiprocessing` занимает больше времени, чем версия с одним процессом в python 3 на Linux? - PullRequest
2 голосов
/ 06 апреля 2020

Я пытаюсь применить функцию к большим range числам - и версия, в которой я использую пул из multiprocessing, занимает гораздо больше времени до окончания sh, чем то, что я оцениваю для версии "с одним процессом" -

Это проблема с моим кодом? Или Python? Или Linux?

Используемая мной функция is_solution определена ниже -

as_ten_digit_string = lambda x: f"0000000000{x}"[-10:]

def sum_of_digits(nstr):
    return sum([int(_) for _ in list(nstr)])

def is_solution(x):
    return sum_of_digits(as_ten_digit_string(x)) == 10

Когда я запускаю is_solution для миллиона чисел - это занимает около 2 секунд

In [13]: %timeit [is_solution(x) for x in range(1_000_000)]                                                                                                        
1.9 s ± 18.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Исходя из этого - для ~ 10 миллиардов номеров - это должно занять около 20000 секунд или около 6 часов. Но версия multiprocessing не заканчивается даже через 9 часов.

Я использую такой многопроцессорный модуль -

from multiprocessing import Pool
with Pool(processes=24) as p:
    for solution in p.imap_unordered(is_solution, range(1_000_000_000, 9_999_999_999)):
        if solution:
            print(solution)

Используемая версия python - 3.8 на linux.

Я не знаю, относится ли это к делу - когда я запускаю команду top в linux - я вижу, что когда моя основная программа работает в течение ~ 200 минут - каждый из моих рабочих процессов имеет Время работы процессора около 20 минут.

Ответы [ 2 ]

1 голос
/ 07 апреля 2020

Многопроцессорная обработка не бесплатна. Если у вас есть ядра X-процессоров, то порождение более чем X-процессов в конечном итоге приведет к снижению производительности. Если ваши процессы выполняют ввод / вывод, тогда процесс 10 * X может быть нормальным. Потому что они не напрягают процессор. Однако, если ваши процессы выполняют вычисления и манипулируют памятью, возможно, что любой процесс выше X только снижает производительность. В комментариях вы сказали, что у вас 4 ядра, поэтому вы должны установить Pool(processes=4). Вы можете поэкспериментировать с разными значениями. Многопроцессорность сложна, возможно, что 5 или даже 8 процессов все равно повысят производительность. Но очень вероятно, что 24 процесса с четырьмя ядрами процессора только снижают производительность.

Еще одна вещь, которую вы можете сделать, это отправлять данные подпроцессам в пакетном режиме. В тот момент, когда вы отправляете данные одну за другой, и поскольку ваши вычисления бывают быстрыми (для единственной точки данных), возможно, межпроцессное взаимодействие доминирует в общем времени выполнения. Это цена, которую вы не платите в сценарии с одним процессом, но всегда платите при многопроцессорной обработке. Чтобы минимизировать его эффект, используйте параметр chunksize, равный imap_unordered.

Наконец, попробуйте переопределить ваш алгоритм, чтобы избежать грубой силы. По предложению @ Alex.

1 голос
/ 06 апреля 2020
def solution(n, sum):
    """Generates numbers of n digits with the given total sum"""
    if n == 1 and sum < 10:
        yield str(sum)
        return
    if n < 1 or (sum > 9 and n < 2):
        return 
    if sum == 0:
        yield "0" * n
        return
    for digit in range(min(sum + 1,  10)):
        for s in solution(n - 1, sum - digit):
            yield str(digit) + s        

# Print all 4-digits numbers with total sum 10
for s in solution(4, 10):
    print(s)

# Print all 4-digits numbers with total sum 10, not starting with zero
for digit in range(1, 10):
    for s in solution(3, 10 - digit):
        print(str(digit) + s)
...