mpi4py с процессами и потоками

Привет. Это довольно специфический вопрос, поэтому я надеюсь, что StackOverflow предназначен для всех языков программирования, а не только для javascript/html.

Я пишу мультипрограмму в MPICH2 (популярный интерфейс передачи сообщений). Моя программа написана на Python, поэтому я использую привязки MPI4Py Python. MPI лучше всего подходит для ситуаций без разделяемой памяти, поэтому он не идеален для многоядерного программирования. Чтобы использовать полные 4 ядра моего кластера из 5 узлов, я дополнительно использую потоки. Однако я заметил, что использование потоков на самом деле замедляет мою симуляцию. Моя программа состоит из нескольких десятков тысяч строк кода, поэтому я не могу все это выложить, но вот фрагмент, вызывающий проблемы

from threading import Thread
...
threadIndeces=[[0,10],[11,20],[21,30],[31,40]] #subset for each thread
for indeces in treadIndeces:
  t=Thread(target=foo,args=(indeces,))
  t.start()

Кроме того, я обязательно присоединюсь к темам позже. Если я запускаю его без потоков и просто вызываю foo со всеми индексами, это примерно в 10-15 раз быстрее. Когда я записываю время многопоточной версии, создание потоков в вызове t=Thread(target=foo,args=(indeces,)) занимает около 0,05 секунды, соединение аналогично занимает 0,05 секунды, но вызовы t.start() занимают колоссальные 0,2 секунды.

Является ли start() дорогим вызовом? Должен ли я изменить свой подход? Я думал о сохранении пула потоков, а не о создании новых на каждой итерации, но не похоже, что t=Thread(target=foo,args=(indeces,)) является причиной замедления.

Кроме того, если кто-то хочет узнать сложность foo, вот одна из функций, которая вызывается i раз для indeces каждой итерации (не дискретное время):

def HD_training_firing_rate(HD_cell):
    """During training, the firing rate is governed by the difference between the 
       current heading direction and the preferred heading direction. This is made
       to resemble a Gaussian distribution
    """
    global fabs
    global exp
    global direction

    #loop over twice due to concurrent CW and CCW HD training
    for c in [0,1]:
        d=direction[c]
        dp=HD_cell.dp  #directional preferance
        s_d=20.0  #standard deviation
        s_i=min(fabs(dp-d),360-fabs(dp-d)) #circular deviation from preferred dir.

        HD_cell.r[c]=exp(-s_i*s_i/(2*s_d*s_d))  #normal distribution

person puk    schedule 07.04.2011    source источник


Ответы (1)


Если вам нужны потоки, python может быть не лучшим вариантом из-за глобальной блокировки интерпретатора, которая предотвращает истинное параллелизм. См. также тревожный разговор Дэйва Бизли.

Возможно, вам лучше просто запустить 20 процессов, чтобы ваши 4 ядра и 5 узлов были заняты, и просто использовать MPI для всех коммуникаций.

Python влечет за собой много накладных расходов на большом железе — вы можете подумать о C или C++ (или, осмелюсь сказать, о Fortran?), если вы действительно привержены совместному подходу к передаче потоков/сообщений.

person Drew Hall    schedule 07.04.2011
comment
+1 - я не уверен, в чем здесь преимущество потоков. Похоже, что каждый поток делает что-то совершенно независимое? Если да, то в чем преимущество использования потоков по сравнению с MPI? Что касается поиска проблем с производительностью, вам лучше исключить из уравнения крупномасштабную декомпозицию и просто сосредоточиться на том, чтобы все работало в одном блоке с общей памятью как можно быстрее, затем снова ввести кросс-узловой материал MPI. Если вы застряли, делая все это на python, вы вполне можете в конечном итоге обнаружить, что подходы, основанные на процессах, такие как многопроцессорность, превосходят потоки. - person Jonathan Dursi; 08.04.2011
comment
@Drew Hall: Сначала я использовал C, но этот язык очень утомительный, Python проще в использовании. - person puk; 08.04.2011
comment
@Drew Hall, Джонатан Дурси: Люди, кажется, забывают, что MPI не предполагает разделяемой памяти, тогда как потоки допускают разделяемую память. Проблема здесь в глобальных переменных. Я много масштабирую, что требует вычисления глобального максимума. С потоками этого можно легко добиться, на самом деле это тривиально. Если я использую MPI и запускаю более одного процесса на одном узле, им придется сообщать эти глобальные максимумы. Общение, простите за мой язык, заноза в заднице. - person puk; 08.04.2011
comment
@puk: даже при наличии нескольких процессов вы должны иметь возможность открывать сегмент общей памяти для процессов на одном узле. Я не знаю, как это сделать в питоне, но я был бы удивлен, если бы это невозможно было сделать. - person Drew Hall; 08.04.2011
comment
@Drew Hall: я просто хотел указать, почему я решил использовать потоки в дополнение к MPI. Я не уверен на 100%, но на 99% уверен, что через MPI нельзя иметь разделяемую память. - person puk; 08.04.2011
comment
@puk, конечно, MPI не предоставляет общую память (хотя и использует ее там, где она доступна), но mpi_allreduce() тривиально позволяет вам найти глобальные максимумы (например). - person Jonathan Dursi; 08.04.2011
comment
@Drew: я отказался от таких функций, как reduce и gather, когда использовал MPI с C. Я заметил, что это приводит к линейному снижению производительности. Я отслеживал свою программу в течение нескольких недель и в конце концов обнаружил, что причиной является эта функция. Тем не менее, я не думаю, что буду использовать здесь слово «тривиальный», например, что, если ваша программа меньше, чем количество узлов (т.е. симулирует 100 нейронов на 200 компьютерах), то вы «можете» вызвать ошибку когда вы пытаетесь уменьшить ранги, которые равны нулю. Это можно легко запрограммировать, но ничто не сравнится с общей памятью ;-) - person puk; 11.04.2011