2017-02-07 4 views
0

Я хочу распараллеливания итерации, в которой многие экземпляры экземпляров Cython оцениваются и результаты хранятся в глобальном массиве Numpy:распараллеливания питон: многопроцессорных против Cython

for cythonInstance in myCythonInstances: 
    success = cythonInstance.evaluate(someConstantGlobalVariables,) # very CPU intense 
    if success == False: 
     break 
    globalNumpyArray[instanceSpecificLocation] = cythonInstance.resultVector[:] 

Результаты оценок экземпляров независимо друг от друга. Между экземплярами нет никакого взаимодействия, за исключением того, что результаты записываются в один и тот же глобальный массив, но в фиксированных, заранее определенных и независимых местах. Если одна оценка не удалась, итерация должна быть остановлена.

Насколько я понял, возможны 2 возможности: 1) с использованием пакета многопроцессорной обработки 2) создание функции cython и использование prange/openmp.

У меня нет опыта с распараллеливанием. Какое решение предпочтительнее, или есть ли лучшие альтернативы? Спасибо!

+0

Следует отметить, что эта итерация называется довольно часто (1000x +). – macmallow

ответ

2

Используйте Cython, если вы можете:

  1. Синтаксис prange очень похож на range. Это позволяет вам легко проложить путь для создания цикла Python -> преобразовать его в Cython -> преобразовать его в параллельный цикл. Надеемся, что изменения, необходимые каждый раз, малы. Напротив, многопроцессорность требует, чтобы вы получили внутреннюю часть вашего цикла как функцию, а затем настроили ppols, поэтому он стал менее знакомым.

  2. Профили OpenMP/Cython довольно низкие. Напротив, многопроцессорный модуль имеет относительно высокие служебные данные («процессы», как правило, медленнее, чем «потоки»).

  3. Многопроцессорность довольно ограничена в Windows (все должно быть разборчиво). Это часто оказывается довольно хлопотным.

Там в нескольких конкретных обстоятельствах, когда вы должны использует многопроцессорный:

  1. Вы находите, что Вы должны получить Gil много - многопроцессорный не разделяют GIL так не замедляется. Если вам нужно только время от времени получать GIL, хотя небольшие блоки with gil: в Cython часто не замедляют вас слишком сильно, поэтому попробуйте это первым.

  2. Вам нужно сделать кучу совершенно разных операций сразу (то есть что-то, что не поддается циклу prange, потому что каждый поток действительно работает отдельным кодом). Если это так, синтаксис Cython prange не подходит.


предостережений от глядя на ваш код, что вы должны избегать использования классов Cython, если вы можете. Если вы можете реорганизовать его на вызов функции cdef, которая будет лучше (классы Cython по-прежнему будут нуждаться в GIL время от времени). Нечто похожее на следующее будет хорошо работать:

cdef int f(double[:] arr, double loop_specific_parameter, int constant_parameter) nogil: 
    # return your boolean to stop the iteration 
    # modify arr 
    return result 

# then elsewhere 
cdef int i 
cdef double[:,:] output = np.zeros(shape) 
for i in prange(len(parameters_to_try),nogil=True): 
    result = f(output[i,:],parameters_to_try[i],constant_parameter) 
    if result: 
     break 

причина, почему я не рекомендую с помощью классов Cython является то, что 1) вы не можете создавать их или индекс списка из них без GIL (для подсчета ссылок причины) и 2) объекты Python, включая классы Cython, по-видимому, не могут быть потоковыми локальными. См. Cython parallel prange - thread locality? для примера проблем. (Первоначально я не знал об ограничении на то, чтобы быть местным местным)

Накладные расходы with_gil не обязательно огромны, поэтому, если этот дизайн имеет наибольший смысл, попробуйте его. Глядя на использование вашего процессора, вы узнаете, насколько хорошо он распараллеливается.


Nb. Большинство плюсов/минусов в this set of answers по-прежнему применяется, хотя вы используете Cython, а не модуль потоковой передачи Python. Разница в том, что вы часто можете избежать GIL в Cython (поэтому некоторые из недостатков использования потоков менее значительны).

+0

Благодарим вас за подробный ответ! Я дам ему попробовать. Но можете ли вы объяснить мне, почему я должен избегать классов цитонов, и в этом случае наблюдается значительное замедление в отличие от чистых функций cdef? – macmallow

+0

Может быть, я должен упомянуть, что класс cython в основном является оболочкой для функции C, но также содержит много информации о конкретном экземпляре, которая передается функции C. – macmallow

+0

@macmallow См. Edit, почему вы не должны использовать классы cython. Сколько замедлений действительно зависит от того, сколько работы выполняется в завернутой функции (если это много, то не беспокойтесь, накладные расходы должны быть небольшими в сравнении) – DavidW

1

Я бы предложил использовать joblib с базовым блоком threading. Joblib - очень хороший инструмент для паралеллизации для циклов. Joblib
Threading предпочтительнее для многопроцессорности здесь, потому что многозадачность обработки имеет много накладных расходов. Это было бы неприемлемо, если бы было сделано много параллельных вычислений. Однако результаты сохраняются в списке, который затем можно преобразовать обратно в массив numpy.

from joblib import Parallel, delayed 

def sim(x): 
    return x**2 


if __name__ == "__main__": 

    result = Parallel(n_jobs=-1, backend="threading", verbose=5) \ 
     (delayed(sim)(x) for x in range(10)) 

    print result 

результат

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]