В последнее время я наблюдал странный эффект, когда измерял производительность моего параллельного приложения с использованием модуля многопроцессорности и mpi4py в качестве средств связи.Параллельное приложение в python становится намного медленнее при использовании модуля mpi, а не многопроцессорного модуля
Приложение выполняет эволюционные алгоритмы на наборах данных. Большинство операций выполняются последовательно, за исключением оценки. После применения всех эволюционных операторов все люди должны получать новые значения пригодности, которые выполняются во время оценки. В основном это всего лишь математический расчет, выполненный в списке float (python). Перед оценкой набор данных рассеивается либо разбросом mpi, либо пулом pool.map на python, затем идет параллельная оценка, а затем данные возвращаются через сборник mpi или снова в механизм Pool.map.
Моя тестовая платформа - виртуальная машина (виртуальный бокс), работающая под управлением Ubuntu 11.10 с открытым MPI 1.4.3 на Core i7 (4/8 ядер), 8 ГБ оперативной памяти и SSD-накопитель.
Что я нахожу действительно удивительным, так это то, что я получаю приятное ускорение, однако, в зависимости от инструмента связи, после определенного порога процессов производительность ухудшается. Это можно проиллюстрировать на рисунках ниже.
ось у - время обработки
оси х - NR процессов
цветов - размер каждого индивидуума (№ поплавков)
1) Используя модуль многопроцессорной обработки - Pool.map
2) Использование МПИ - Scatter/Gather
3) Обе картины на верхней части друг друга
Сначала я подумал, что это вина HyperThreading, потому что для наборов данных большого становится медленнее после достижения 4 процесса (4 физических ядер). Однако он также должен быть виден в многопроцессорном корпусе, а это не так. Мое другое предположение, что методы коммуникации mpi намного менее эффективны, чем python, однако мне трудно поверить.
У кого-нибудь есть объяснение этих результатов?
ДОБАВЛЕНО:
Я начинаю верить, что это вина Hyperthreading в конце концов. Я тестировал свой код на машине с ядром 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 - Scatter/Gather
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 Xeons (2x 6/12 ядер) и повторил тест:
Теперь у меня есть 3 примера одного и того же поведения. Когда я запускаю вычисления в большем количестве процессов, чем физические ядра, он начинает ухудшаться. Я считаю, что это связано с тем, что процессы на одном физическом ядре не могут выполняться одновременно из-за нехватки ресурсов.
Реализации MPI обычно имеют множество различных алгоритмов для коллективных операций, таких как разброс и сбор. Большинство библиотек имеют собственную эвристику, чтобы выбрать лучший алгоритм, но иногда он терпит неудачу. Некоторые библиотеки позволяют принудительно выполнять эти алгоритмы, например. в Open MPI это может быть достигнуто путем передачи аргументов MCA в 'mpiexec'. Это поможет, если вы сообщите нам, какую реализацию MPI вы используете. –
Я использую Open MPI, добавленный в вопрос. – Michal
Было бы здорово, если бы вы могли предоставить больше контекста, например, показать части кода, в котором данные распределяются (обе реализации). Обычно разбросаны и собираются в Open MPI-шкале очень хорошо с количеством процессов, когда все они работают на одном узле. –