В продолжение выпуска 2.
Чтож, попробуем ускорить нашу функцию ещё больше.
Первое, что бросается в глаза при анализе работы функции из выпуска 2 void f3(double * pdArr)
так это неполная загрузка ядер в следствии не очень хорошей балансировки циклов. На этот случай у OpenMP так же есть инструменты. Для балансировки циклов используется директива schedule(type[,size]), подробнее можно почитать тут.
Не вдаваясь в подробности скажу, что наилучший результат дал вариант #pragma omp parallel for schedule(dynamic, 8192)
. На всякий случай, в качестве параметра, я использую величину, являющейся степенью двойки, на такое число компилятору всяко проще делить (если это ему поможет, а мне уж точно не навредит). Так какой же результат получается с такой директивой? 1.72 секунды, что 2.5 раза быстрее чем однопоточная версия функции. Использование других дериктив и других чисел в размере блока так же может давать прирост в ускорении но не такой значительный. Уменьшение размера блока увеличивает накладные расходы на обслуживание, увеличение — ухудшает балансировку. Т.е. размер блока надо подбирать индивидуально в зависимости от объёма работы, совершаемой в самом теле цикла. И на последок картина, как выглядит выполнение оптимизированной программы в Intel Thread Profiler:
На картинке представлено (как и в прошлый раз) небольшой участок выполнения программы (примерно 4 запуска параллельного цикла). Видно, что балансировка циклов стала почти идеальной (все потоки заканчивают работу практически одновременно). И даже в случае задержки запуска некоторых потоков (как в третьем цикле на картинке) все они завершают работу в один момент. Мы достигли 2.5 кратного ускорения цикла при распараллеливании его на 8 ядер. Результат достаточно посредственный, но это всё же ускорение по сравнению с однопоточной версией. В следующем выпуске исследуем причины такого не самого яркого результата.
Метки: C++, OpenMP