Параллельная обработка Python - различное поведение между Linux и Windows - PullRequest
0 голосов
/ 02 ноября 2018

Я пытался сделать мой код параллельным, и я столкнулся со странной вещью, которую я не в состоянии объяснить.

Позвольте мне определить контекст. У меня действительно тяжелые вычисления: чтение нескольких файлов, анализ машинного обучения, много математики. Мой код работает нормально в Windows и Linux, когда это последовательно, но когда я пытаюсь использовать многопроцессорность, все ломается. Ниже приведен пример, который я сначала разработал для Windows:

from multiprocessing.dummy import Pool as ThreadPool 

def ppp(element):
    window,day = element
    print(window,day)
    time.sleep(5)
    return

if __name__ == '__main__'    
    #%% Reading datasets
    print('START')
    start_time = current_milli_time()
    tree = pd.read_csv('datan\\days.csv')
    days = list(tree.columns)
    # to be able to run this code uncomment the following line and comment the previous two
    # days = ['0808', '0810', '0812', '0813', '0814', '0817', '0818', '0827', '0828', '0829']
    windows = [1000]
    processes_args = list(itertools.product(windows, days))

    pool = ThreadPool(8) 
    results = pool.map_async(ppp, processes_args)
    pool.close() 
    pool.join() 
    print('END', current_milli_time()-start_time, 'ms')

Когда я запускаю этот код в Windows, вывод выглядит так:

START
100010001000 1000 1000100010001000      081008120808
08130814
0818
082708171000
1000    
  08290828

END 5036 ms

Грязный набор отпечатков за 125 мс. Такое же поведение и в Linux. Однако я заметил, что если я применяю этот метод в Linux и смотрю на «htop», я вижу набор потоков, которые выбираются случайным образом для выполнения, но никогда не выполняются параллельно. Таким образом, после некоторых поисков в Google, я получил новый код:

from multiprocessing import Pool as ProcessPool

def ppp(element):
    window,day = element
    print(window,day)
    time.sleep(5)
    return

if __name__ == '__main__':
    #%% Reading datasets
    print('START')
    start_time = current_milli_time()
    tree = pd.read_csv('datan\\days.csv')
    days = list(tree.columns)
    # to be able to run this code uncomment the following line and comment the previous two
    # days = ['0808', '0810', '0812', '0813', '0814', '0817', '0818', '0827', '0828', '0829']
    windows = [1000]
    processes_args = list(itertools.product(windows, days))

    pool = ProcessPool(8) 
    results = pool.map_async(ppp, processes_args)
    pool.close() 
    pool.join() 
    print('END', current_milli_time()-start_time, 'ms')

Как видите, я изменил оператор импорта, который в основном создает пул процессов вместо пула потоков. Это решает проблему в Linux, фактически в реальном сценарии у меня 8 процессоров, работающих на 100%, и 8 процессов, работающих в системе. Вывод выглядит как предыдущий. Тем не менее, когда я использую этот код в Windows, требуется более 10 секунд для всей работы, более того, я не получаю отпечатки ppp, а только основные.

Я действительно пытался найти объяснение, но я не понимаю, почему это происходит. Например, здесь: Python multiprocessing Pool странное поведение в Windows , они говорят о безопасном коде на окнах, и ответ предлагает перейти к Threading, что, как побочный эффект, сделает код не параллельным, а параллельным , Вот еще один пример: Разница в многопроцессорности Linux Python Windows . Все эти вопросы описывают процессы fork() и spawn, но я лично считаю, что смысл моего вопроса не в этом. Документация Python по-прежнему объясняет, что в Windows нет метода fork() (https://docs.python.org/2/library/multiprocessing.html#programming-guidelines).

В заключение, сейчас я убежден, что я не могу выполнять параллельную обработку в Windows, но я думаю, что то, что я получаю в результате всех этих обсуждений, неверно. Таким образом, у меня должен возникнуть вопрос: возможно ли параллельно запускать процессы или потоки (на разных процессорах) в Windows?

РЕДАКТИРОВАТЬ: добавить имя == main в обоих примерах

EDIT2: чтобы иметь возможность запускать код этой функции и необходим этот импорт:

import time
import itertools    
current_milli_time = lambda: int(round(time.time() * 1000))

Ответы [ 2 ]

0 голосов
/ 08 ноября 2018

Вы можете выполнять параллельную обработку под Windows (у меня сейчас запущен скрипт, выполняющий тяжелые вычисления и использующий 100% всех 8 ядер), но он работает путем создания параллельных процессов , а не потоки (которые не будут работать из-за GIL, за исключением операций ввода-вывода). Несколько важных моментов:

  • вам нужно использовать concurrent.futures.ProcessPoolExecutor() (обратите внимание, что это пул процессов, а не пул потоков). См. https://docs.python.org/3/library/concurrent.futures.html. В двух словах, как это работает, вы помещаете код, который вы хотите распараллелить, в функцию, а затем вызываете executor.map(), который выполнит разделение.
  • обратите внимание, что в Windows каждый параллельный процесс будет начинаться с нуля, поэтому вам, вероятно, придется использовать if __name__ == '__main__:' в нескольких местах, чтобы отличать то, что вы делаете в основном процессе от других. Данные, которые вы загружаете в основной сценарий, будут реплицированы на дочерние процессы, поэтому они должны быть сериализуемыми (можно выбрать в языке Python).
  • для эффективного использования ядра избегайте записи данных в объекты, общие для всех процессов (например, счетчик хода выполнения или общая структура данных). В противном случае синхронизация между процессами снизит производительность. Поэтому следите за выполнением из диспетчера задач.
0 голосов
/ 02 ноября 2018

под windows, python использует pickle / unpickle для имитации fork в многопроцессорном модуле, при выполнении unpickle модуль повторно импортируется, любой код в глобальной области действия выполняется снова, документы заявлено:

Вместо этого следует защищать «точку входа» программы, используя if name == ' main '

кроме того, вы должны указать AsyncResult, возвращаемое pool.map_async, или просто использовать pool.map.

...