Параллельное приложение в Python становится намного медленнее при использовании mpi, а не многопроцессорного модуля

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

Приложение выполняет эволюционные алгоритмы на наборах данных. Большинство операций выполняются последовательно, за исключением оценки. После применения всех эволюционных операторов всем индивидуумам необходимо получить новые значения приспособленности, что и делается во время оценки. По сути, это просто математический расчет, выполняемый для списка чисел с плавающей запятой (python). Перед оценкой набор данных рассредоточен либо из-за разброса mpi, либо из-за Pool.map python, затем идет параллельная оценка, а затем данные возвращаются через сборку mpi или снова через механизм Pool.map.

Моя тестовая платформа - это виртуальная машина (виртуальный бокс) под управлением Ubuntu 11.10 с Open MPI 1.4.3 на Core i7 (4/8 ядер), 8 ГБ ОЗУ и SSD-накопитель.

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

ось y - время обработки
ось x - количество процессов
цвета - размер каждого отдельного элемента (количество чисел с плавающей запятой)

1) Использование модуля многопроцессорности - Pool.map введите описание изображения здесь

2) Использование mpi - Scatter / Gather введите описание изображения здесь

3) Оба изображения накладываются друг на друга введите описание изображения здесь

Сначала я подумал, что это ошибка гиперпоточности, потому что для больших наборов данных он становится медленнее после достижения 4 процессов (4 физических ядра). Однако это также должно быть видно в случае многопроцессорной обработки, а это не так. Еще я предполагаю, что методы связи mpi намного менее эффективны, чем методы python, но мне трудно в это поверить.

Есть ли у кого-нибудь объяснения этим результатам?

ДОБАВЛЕНО:

Я начинаю верить, что это все-таки ошибка Hyperthreading. Я тестировал свой код на машине с Core i5 (2/4 ядра), и производительность хуже с 3 или более процессами. Единственное объяснение, которое приходит мне в голову, это то, что у i7, который я использую, недостаточно ресурсов (кеша?) Для вычисления оценки одновременно с Hyperthreading, и ему нужно запланировать более 4 процессов для запуска на 4 физических ядрах.

Однако что интересно, когда я использую mpi, htop показывает полное использование всех 8 логических ядер, что должно указывать на то, что приведенное выше утверждение неверно. С другой стороны, когда я использую Pool.Map, он не полностью использует все ядра. Он использует один или два по максимуму, а остальные только частично, опять же, не знаю, почему он так себя ведет. Завтра прикреплю скриншот, показывающий такое поведение.

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

Pool.map:

def eval_population(func, pop):
    for ind in pop:
        ind.fitness.values = func(ind)

    return pop

# ...
self.pool = Pool(8)
# ...

for iter_ in xrange(nr_of_generations):
    # ...
    self.pool.map(evaluate, pop) # evaluate is really an eval_population alias with a certain function assigned to its first argument.
    # ...

MPI - разброс / сбор

def divide_list(lst, n):
    return [lst[i::n] for i in xrange(n)]

def chain_list(lst):
    return list(chain.from_iterable(lst))

def evaluate_individuals_in_groups(func, rank, individuals):
    comm = MPI.COMM_WORLD
    size = MPI.COMM_WORLD.Get_size()

    packages = None
    if not rank:
        packages = divide_list(individuals, size)

    ind_for_eval = comm.scatter(packages)
    eval_population(func, ind_for_eval)

    pop_with_fit = comm.gather(ind_for_eval)

    if not rank:
        pop_with_fit = chain_list(pop_with_fit)
        for index, elem in enumerate(pop_with_fit):
            individuals[index] = elem

for iter_ in xrange(nr_of_generations):
        # ...
        evaluate_individuals_in_groups(self.func, self.rank, pop)
        # ...

ДОБАВЛЕНО 2: как я упоминал ранее, я провел несколько тестов на своей машине i5 (2/4 ядра) и вот результат:  введите описание изображения здесь

Я также нашел машину с 2 xeon (2 ядра 6/12) и повторил тест: введите описание изображения здесь

Теперь у меня есть 3 примера такого же поведения. Когда я запускаю свои вычисления в большем количестве процессов, чем физических ядер, ситуация начинает ухудшаться. Я считаю, что это потому, что процессы на одном физическом ядре не могут выполняться одновременно из-за нехватки ресурсов.


person Michal    schedule 11.06.2013    source источник
comment
Реализации MPI обычно имеют много разных алгоритмов для коллективных операций, таких как разброс и сборка. В большинстве библиотек есть собственные эвристики для выбора лучшего алгоритма, но иногда это не удается. Некоторые библиотеки позволяют принудительно использовать эти алгоритмы, например в Open MPI это может быть достигнуто путем передачи аргументов MCA в mpiexec. Было бы полезно, если бы вы сказали нам, какую реализацию MPI вы используете.   -  person Hristo Iliev    schedule 12.06.2013
comment
Использую Open MPI, добавил к вопросу.   -  person Michal    schedule 12.06.2013
comment
Было бы здорово, если бы вы могли предоставить больше контекста, например, показать части кода, по которым распределяются данные (обе реализации). Обычно разброс и сборка в Open MPI очень хорошо масштабируются с количеством процессов, когда все они работают на одном узле.   -  person Hristo Iliev    schedule 12.06.2013
comment
Я мог бы глубже разобраться в вашей проблеме, но только после того, как ISC'13 закончится в конце следующей недели. Надеюсь, что до тех пор ты найдешь виновного.   -  person Hristo Iliev    schedule 13.06.2013
comment
Вы когда-нибудь выясняли причины? Это именно те результаты, которые я получаю на 2-х процессном 6-ядерном xeon.   -  person user-2147482637    schedule 20.03.2015
comment
@HristoIliev за ваш первый комментарий, почему это повлияет на производительность процесса загрузки файла? Загрузка файла с достаточным количеством ядер, в котором используется гиперпоточность, удваивает время чтения этого файла по сравнению с многопроцессорной обработкой, которая остается согласованной.   -  person user-2147482637    schedule 20.03.2015


Ответы (1)


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

mpi4py делает копию для каждого сообщения, поскольку он нацелен на использование распределенной памяти. Если ваш OpenMPI не настроен на использование sharedmemory для связи внутри узла, это сообщение будет отправлено через стек TCP ядра и обратно, чтобы доставить его другому процессу, который снова добавит некоторые накладные расходы.

Если вы собираетесь выполнять вычисления только на одном компьютере, здесь нет необходимости использовать mpi.

Некоторые из них обсуждаются в этой теме.

Обновление ipc-benchmark пытается понять, как разные типы коммуникации работают в разных системах. (многоядерность, многопроцессорность, разделяемая память) И особенно как это влияет на виртуализированные машины!

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

person Jens Timmerman    schedule 21.06.2013
comment
Я работаю над приложением для тестирования эволюционных алгоритмов в различных вычислительных средах: ПК, кластеры, сетка и т. Д. Я не спорю, оправдано ли здесь использование MPI. Мне просто любопытно, откуда берутся накладные расходы. И, кстати, python pool.map использует очередь для связи, которая реализуется либо с помощью каналов, либо с помощью сокетов. Я настроил его на использование сокетов, поэтому способ связи такой же. - person Michal; 22.06.2013
comment
@Michal, это не потому, что вы используете сокеты, вы используете tcp. Вот несколько интересных слайдов о различных скоростях, которые вы можете получить при использовании каналов, сокетов или shmem, + инструмент для тестирования, который расскажет вам, что может быть самым быстрым в вашей системе (в зависимости от того, как ваши ядра могут взаимодействовать, как ваши сокеты процессора могут взаимодействовать и их домен numa.) anil.recoil.org/talks/fosdem-io -2012.pdf - person Jens Timmerman; 23.06.2013
comment
Спасибо что подметил это. Я проверю, что вы написали, и сайт, который вы дали, когда у меня будет время. - person Michal; 25.06.2013