5

Я пишу программу на C++ для моделирования конкретной системы. Для каждого временного интервала большая часть выполнения занимает один цикл. К счастью, это смущает параллель, поэтому я решил использовать Boost Threads для его распараллеливания (я работаю на двухъядерном компьютере). Я ожидаю, что при ускорении будет близка к 2-кратной серийной версии, так как нет блокировки. Однако я нахожу, что ускорения вообще нет.Параллельная версия цикла не быстрее, чем серийная версия

Я реализовал параллельную версию петли следующим образом:

  • разбудить две нити (они заблокированы на барьер).
  • Каждый поток затем выполняет следующие действия:

    • Атомарно выборки и приращение глобального счетчика.
    • Извлеките частицу с этим индексом.
    • Выполните вычисление на этой частице, сохраняя результат в отдельном массиве
    • Подождите на работах готового барьере
  • Основные поток ожидает заданий законченного барьера.

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

Если у кого-то есть идеи, что искать или какие-либо намеки, я бы очень признателен. Я пробивал себе голову на неделю, и профилирование не показало много.

EDIT: проблема решена! Я расскажу, как я решил эту проблему. Я снова использовал gprof, но на этот раз скомпилирован без флага оптимизации (-O3). Сразу же профайлер указал, что я проводил невероятное количество времени в функции, которая выполняет вычисления на каждой отдельной частице: намного больше, чем в серийной версии.

Эта функция является виртуальной и доступ к ней осуществляется полиморфно. Я изменил код, чтобы получить доступ к нему напрямую, а не через vtable и voila. Параллельная версия произвела ускорение почти на 2! Такое же изменение в серийной версии мало повлияло.

Я не уверен, почему это так, и было бы интересно, если кто-нибудь знает!

Спасибо всем плакатам. Вы все помогли в некоторой степени, и будет очень сложно принять один ответ.

+1

Интересно, вы пробовали синхронизировать только цикл? Нет ли улучшения вообще или просто неутешительное улучшение? Вы проверили в диспетчере задач, что ваше приложение фактически создает два потока? Может быть, ваша память приложений интенсивна, возможно, у вас есть узкое место в памяти? Вы также можете попытаться просто разделить массив и позволить каждому потоку обрабатывать одну половину, просто чтобы увидеть, будет ли какая-либо разница. – Ivan

+0

Да, я приурочен и профилировал только цикл и ничего больше. Существует неутешительное улучшение: ускорение варьируется от 0,9 до 1,1. Диспетчер задач показывает, что оба процессора очень заняты. Нити не выделяют никакой новой памяти. Единственная запись в один массив, и они пишут в независимых местах, и они никогда не читают из того же массива. Когда я разбил массив на два, я получил аналогичную производительность. –

ответ

2

Выполните вычисление на этой частицы, сохраняя результат в отдельном массиве

Как тяжелы вычисления?

  • Вообще говоря атомарный счетчик может стоить сотни тактов, и это очень важно, чтобы видеть, что вы не только увеличивают значения счетчиков.
  • Также попробуйте посмотреть, сколько работы выполняют каждая нить - они хорошо взаимодействуют (т. Е. На каждый цикл каждый продолжается примерно половиной частицы).
  • Попробуйте разделить работу на более крупные куски, а затем на частицу (скажем, 100 частиц и т. Д.).
  • Посмотрите, как много работы выполняется за пределами потоков.

Честно говоря ... похоже, что вы говорите, это ошибка.

+0

Вычисления могут быть довольно тяжелыми, и они варьируются от нескольких миллисекунд до нескольких секунд. Нити независимы друг от друга, и я проверил время их выполнения и примерно равное, так что происходит балансировка нагрузки. Я попробую ваше предложение рассмотреть 100 частиц вместо 1 теперь –

+0

>> с нескольких миллисекунд, чтобы приблизиться ко второму << если так .. используя 100 частиц не помогло бы. Что может случиться, что один поток завершается быстро и слишком много ждет для другого потока на барьере, который выполняет длинные прогоны ... Измерьте много времени, когда каждый поток запускается до того, как он достигнет барьера. – Artyom

1

profiling has not revealed much

Это неясно. У меня есть опыт профилирования многопоточного приложения на HP-UX, и там их профайлер говорит, что процент времени каждой функции работает. Поэтому, если у вас есть одна или несколько точек соперничества в ваших функциях, вы получаете увеличение времени, которое ваше приложение тратит на эти функции. В моем случае я получил значительное увеличение в pthread_mutex_unlock(). Когда я сменил код, он стал намного быстрее.

Итак, можете ли вы разместить здесь одну и ту же статистику для одного потока и для двух/четырех потоков. И количество вычислений в каждом тесте.

Также я рекомендую вам (если возможно) установить точку останова для глобальной функции, блокирующей мьютекс. Вы можете обнаружить, что где-то в вашем алгоритме вы случайно блокируете глобальный мьютекс.

0

Вы говорите, что профилирование не выявило много, и это (к сожалению) типично.

Вот что я хотел бы сделать:

  1. Вернуться к Однозаходному.

  2. Сделайте эту нить как можно быстрее, используя this profiling technique that works in any language and environment. Причина в том, что профилировщики (большинство, но не все) хороши только в , измеряя изменения, а не , определяя, что вы должны исправить.

  3. Затем вернитесь к 1-нити в ядре и выполните этот процесс еще раз. Если вы обнаружите, что один поток, или другой, тратит много времени на межпроцессное общение, вам необходимо переработать это.

+0

1) В течение последних нескольких месяцев один код потока был избит в форму, я был бы очень удивлен, если бы его можно было сделать быстрее, не изменяя динамику модели, которую я имитирую. 2) Я действительно поклонник этого метода. Я помню, как некоторое время назад читал ваш пост :) 3) К сожалению, связи между процессами вообще не должно быть. Это тайна. –

+0

@ Ил-Бхима: Мне все равно было бы очень интересно попробовать, потому что это ничего не стоило, и он никогда не рассказывал мне, что происходит (и не происходит). Это, как правило, очень образовательный. Например, вы говорите, что межпроцессного общения не должно быть. Вы можете заменить это «должно быть» на «is» или «is not». –

1

Ваш язык вид выявления:

Подождите на ххх

это может быть вашей проблемой.


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

+0

Я также подозреваю, что может быть проблема. Таким образом, я заменил барьеры спин-замками, и я все еще получаю такую ​​же неутешительную работу. У меня есть счетчики производительности, основанные на clock() в конце большинства функций, которые включены при компиляции в режиме «DEBUG». Они показывают, что обе нити выполняют примерно равный объем работы. –

0

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

Оцените это как можно скорее.

Смежные вопросы