В последнее время я наблюдал странный эффект, когда измерял производительность своего параллельного приложения, используя модуль многопроцессорности и 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 примера такого же поведения. Когда я запускаю свои вычисления в большем количестве процессов, чем физических ядер, ситуация начинает ухудшаться. Я считаю, что это потому, что процессы на одном физическом ядре не могут выполняться одновременно из-за нехватки ресурсов.
mpiexec
. Было бы полезно, если бы вы сказали нам, какую реализацию MPI вы используете. - person Hristo Iliev   schedule 12.06.2013