Выполнение кода Python в новом процессе намного медленнее, чем в основном процессе - PullRequest
0 голосов
/ 15 мая 2018

Я начал изучать multiprocessing в python и заметил, что тот же код выполняется в основном процессе намного быстрее, чем в процессе, который создается с помощью модуля multiprocessing.

Вот упрощенный пример моего кода, где я сначала выполняю код на main process и время печати для первых 10 вычислений и время для общего вычисления. И затем тот же код выполняется на new process (это длительный процесс, при котором я могу отправить new_pattern в любое время).

import multiprocessing
import random
import time


old_patterns = [[random.uniform(-1, 1) for _ in range(0, 10)] for _ in range(0, 2000)]
new_patterns = [[random.uniform(-1, 1) for _ in range(0, 10)] for _ in range(0, 100)]


new_pattern_for_processing = multiprocessing.Array('d', 10)
there_is_new_pattern = multiprocessing.Value('i', 0)
queue = multiprocessing.Queue()


def iterate_and_add(old_patterns, new_pattern):
    for each_pattern in old_patterns:
        sum = 0
        for count in range(0, 10):
            sum += each_pattern[count] + new_pattern[count]


print_count_main_process = 0
def patt_recognition_main_process(new_pattern):
    global print_count_main_process
    # START of same code on main process
    start_main_process_one_patt = time.time()
    iterate_and_add(old_patterns, new_pattern)
    if print_count_main_process < 10:
        print_count_main_process += 1
        print("Time on main process one pattern:", time.time() - start_main_process_one_patt)
    # END of same code on main process


def patt_recognition_new_process(old_patterns, new_pattern_on_new_proc, there_is_new_pattern, queue):
    print_count = 0
    while True:
        if there_is_new_pattern.value:
            #START of same code on new process
            start_new_process_one_patt = time.time()
            iterate_and_add(old_patterns, new_pattern_on_new_proc)
            if print_count < 10:
                print_count += 1
                print("Time on new process one pattern:", time.time() - start_new_process_one_patt)
            #END of same code on new process
            queue.put("DONE")
            there_is_new_pattern.value = 0


if __name__ == "__main__":
    start_main_process = time.time()
    for new_pattern in new_patterns:
        patt_recognition_main_process(new_pattern)
    print(".\n.\n.")
    print("Total Time on main process:", time.time() - start_main_process)

    print("\n###########################################################################\n")

    start_new_process = time.time()
    p1 = multiprocessing.Process(target=patt_recognition_new_process, args=(old_patterns, new_pattern_for_processing, there_is_new_pattern, queue))
    p1.start()
    for new_pattern in new_patterns:
        for idx, n in enumerate(new_pattern):
            new_pattern_for_processing[idx] = n
        there_is_new_pattern.value = 1
        while True:
            msg = queue.get()
            if msg == "DONE":
                break
    print(".\n.\n.")
    print("Total Time on new process:", time.time()-start_new_process)

А вот и мой результат:

Time on main process one pattern: 0.0025289058685302734
Time on main process one pattern: 0.0020127296447753906
Time on main process one pattern: 0.002008199691772461
Time on main process one pattern: 0.002511262893676758
Time on main process one pattern: 0.0020067691802978516
Time on main process one pattern: 0.0020036697387695312
Time on main process one pattern: 0.0020072460174560547
Time on main process one pattern: 0.0019974708557128906
Time on main process one pattern: 0.001997232437133789
Time on main process one pattern: 0.0030074119567871094
.
.
.
Total Time on main process: 0.22810864448547363

###########################################################################

Time on new process one pattern: 0.03462791442871094
Time on new process one pattern: 0.03308463096618652
Time on new process one pattern: 0.034590721130371094
Time on new process one pattern: 0.033623456954956055
Time on new process one pattern: 0.03407788276672363
Time on new process one pattern: 0.03308820724487305
Time on new process one pattern: 0.03408670425415039
Time on new process one pattern: 0.0345921516418457
Time on new process one pattern: 0.03710794448852539
Time on new process one pattern: 0.03358912467956543
.
.
.
Total Time on new process: 4.0528037548065186

Почему такая большая разница во времени выполнения?

Ответы [ 2 ]

0 голосов
/ 15 мая 2018

Это немного неуловимо, но проблема в

new_pattern_for_processing = multiprocessing.Array('d', 10)

, который не содержит объекты Python float, он содержит необработанные байты, в этом случае достаточно для поддержки 10 8-байтового машинного уровняdouble.Когда вы читаете или пишете в этот массив, python должен конвертировать float в double или наоборот.Это не имеет большого значения, если вы читаете или пишете один раз, но ваш код делает это много раз в цикле, и эти преобразования преобладают.

Для подтверждения я скопировал массив машинного уровня в список Pythonплавает один раз и имеет процесс работы над этим.Теперь его скорость такая же, как у родителя.Мои изменения были только в одной функции

def patt_recognition_new_process(old_patterns, new_pattern_on_new_proc, there_is_new_pattern, queue):
    print_count = 0
    while True:
        if there_is_new_pattern.value:
            local_pattern = new_pattern_on_new_proc[:]
            #START of same code on new process
            start_new_process_one_patt = time.time()
            #iterate_and_add(old_patterns, new_pattern_on_new_proc)
            iterate_and_add(old_patterns, local_pattern)
            if print_count < 10:
                print_count += 1
                print("Time on new process one pattern:", time.time() - start_new_process_one_patt)
            #END of same code on new process
            there_is_new_pattern.value = 0
            queue.put("DONE")
0 голосов
/ 15 мая 2018

В данном конкретном случае вы, похоже, выполняете последовательное выполнение в другом процессе, не распараллеливая свой алгоритм. Это создает некоторые накладные расходы.

Создание процесса требует времени само по себе. Но это еще не все. Вы также передаете данные в очереди и используете прокси-серверы Manager. Все это на практике очереди или фактически две очереди и другой процесс. Очереди очень, очень медленные по сравнению с использованием копий данных в памяти.

Если вы берете свой код, выполняете его в другом процессе и используете очереди для передачи и ввода данных, он всегда медленнее. Что делает это бессмысленным с точки зрения производительности. Тем не менее, для этого могут быть и другие причины, например, если ваша основная программа нужна для чего-то другого, например, для ожидания ввода-вывода.

Если вы хотите повысить производительность, вы должны вместо этого создать несколько процессов и разбить свой алгоритм так, чтобы вы обрабатывали части своего диапазона в разных процессах, таким образом работая параллельно. Вы также можете рассмотреть Multiprocessing.Pool, если хотите иметь группу рабочих процессов, готовых ждать дополнительной работы. Это уменьшит накладные расходы на создание процесса, поскольку вы делаете это только один раз. В Python 3 вы также можете использовать ProcessPoolExecutor.

Параллельная обработка полезна, но это редко змеиное масло, которое решит все ваши проблемы без особых усилий. Чтобы извлечь максимальную выгоду из этого, вам необходимо изменить дизайн своей программы, чтобы максимизировать параллельную обработку и минимизировать передачу данных в очередях.

...