Python: многопроцессорность, пафос и не только

Я должен заранее извиниться, потому что этот вопрос довольно общий и может быть недостаточно ясен. Возникает вопрос: как бы вы запускали параллельно функцию Python, которая сама использует пул процессов для некоторых подзадач и выполняет множество тяжелых операций ввода-вывода? Это вообще актуальная задача?

Я постараюсь предоставить дополнительную информацию. У меня есть процедура, скажем test_reduce(), которую мне нужно запустить параллельно. Я попробовал несколько способов сделать это (см. ниже), и мне, похоже, не хватает знаний, чтобы понять, почему все они терпят неудачу.

Эта test_reduce() процедура делает много вещей. Некоторые из них более актуальны для вопроса, чем другие (и я перечисляю их ниже):

  • Он использует модуль multiprocessing (sic!), а именно экземпляр pool.Pool,
  • Он использует соединение MongoDB,
  • Он сильно зависит от numpy и scikit-learn libs,
  • Он использует обратные вызовы и лямбды,
  • Он использует библиотеку dill для обработки некоторых вещей.

Сначала я попытался использовать multiprocessing.dummy.Pool (который, кажется, является пулом потоков). Я не знаю, что особенного в этом пуле и почему он, э-э, "фиктивный"; все это работало, и я получил свои результаты. Проблема заключается в загрузке процессора. Для распараллеленных секций test_reduce() было 100% для всех ядер; для синхронных участков большую часть времени он составлял около 40-50%. Я не могу сказать, что было какое-то увеличение общей скорости для такого типа "параллельного" выполнения.

Затем я попытался использовать multiprocessing.pool.Pool экземпляр для map этой процедуры с моими данными. Это не удалось со следующим:

File "/usr/lib/python2.7/multiprocessing/pool.py", line 251, in map
    return self.map_async(func, iterable, chunksize).get()
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 558, in get
    raise self._value
cPickle.PicklingError: Can't pickle <type 'thread.lock'>: attribute lookup thread.lock failed

Я предположил, что виноват cPickle, и нашел библиотеку pathos, в которой используется гораздо более продвинутый пиклер dill. Однако это также терпит неудачу:

File "/local/lib/python2.7/site-packages/dill/dill.py", line 199, in load
    obj = pik.load()
  File "/usr/lib/python2.7/pickle.py", line 858, in load
    dispatch[key](self)
  File "/usr/lib/python2.7/pickle.py", line 1083, in load_newobj
    obj = cls.__new__(cls, *args)
TypeError: object.__new__(generator) is not safe, use generator.__new__()

Я совсем не понимаю эту ошибку. У меня нет выходных данных для stdout из моей процедуры, когда она работает в пуле, поэтому трудно предположить, что происходит. Единственное, что я знаю, это то, что test_reduce() успешно работает, когда многопроцессорность не используется.

Итак, как бы вы запускали параллельно что-то такое тяжелое и сложное?


person oopcode    schedule 07.07.2015    source источник
comment
Вам нужно запускать его параллельно, потому что вы не хотите замораживать графический интерфейс? Я был в такой ситуации, и для запуска чего-то тяжелого я использую QT QProcess, который похож на библиотека подпроцессов. Обычно это менее сложно, чем использование нити.   -  person Mel    schedule 07.07.2015
comment
Я предполагаю, что несколько потоков не могут получить доступ к одному и тому же файлу с помощью pickle (или других методов доступа к файлам) одновременно. В качестве возможного решения вы можете использовать другое имя для выходного файла, который вы выбираете для каждого потока (с именем файла, полученным из текущего номера потока). В конце вы можете запустить скрипт для чтения и объединения всех отдельных файлов pickle.   -  person Ed Smith    schedule 07.07.2015
comment
@EdSmith Боюсь, это терпит неудачу задолго до того, как я делаю травление. Я бы сказал, что это multiprocessing (в нем широко используется травление).   -  person oopcode    schedule 07.07.2015
comment
@tmoreau Нет, к сожалению, нет. Я тренирую некоторые сложные модели классификации.   -  person oopcode    schedule 07.07.2015
comment
Использование всех ваших ядер на ~ 50% при большой синхронизации звучит для меня довольно хорошо.   -  person mdurant    schedule 07.07.2015
comment
Ваша настройка может быть настолько сложной, насколько это необходимо, и при этом работать нормально, пока данные, которые вы передаете в отдельные процессы и из них, могут быть обработаны. Вот как многопроцессорная обработка внутренне отправляет данные (аргументы/результаты) туда и обратно. Если вы используете Linux и у вас много данных только для чтения, вы можете воспользоваться тем, что он использует fork и создает копию глобальных данных в каждом дочернем процессе (но изменения не передаются).   -  person bj0    schedule 07.07.2015
comment
Как ты можешь что? Трудно быть конкретным без кода, который можно запустить.   -  person bj0    schedule 08.07.2015
comment
Я автор dill и pathos. Да, можно вложить одну карту в другую. Есть несколько примеров на SO (поиск иерархической параллели).. но map(f1, map(f2, x, y)) абсолютно должен работать, если вы не столкнетесь с проблемой сериализации. Похоже, вы пытаетесь замариновать генератор (i for i in x), с которым dill не справляется.   -  person Mike McKerns    schedule 08.07.2015
comment
@MikeMcKerns Спасибо за ваш ответ. Так может быть и так, что курсор Mongo, который является генератором, вызывает проблему, верно?   -  person oopcode    schedule 08.07.2015


Ответы (1)


Итак, благодаря ответу @MikeMcKerns, я нашел, как выполнить работу с pathos lib. Мне нужно было избавиться от всех курсоров pymongo, которые (будучи генераторами) не могли быть обработаны dill; это решило проблему, и мне удалось запустить свой код параллельно.

person oopcode    schedule 08.07.2015