OpenMP – пожалуй, самая распространённая модель параллельного программирования на потоках, на системах с общей памятью. Ценят её за высокоуровневые параллельные конструкции (в сравнении с программированием системных потоков) и поддержку разными производителями компиляторов. Но этот пост не про сам стандарт OpenMP, про него есть много материалов в сети.
Распараллеливают вычисления на OpenMP ради производительности, о чём, собственно, и статья. Точнее, об измерении производительности с помощью Intel VTune Amplifier XE. А именно, как получить информацию о:
- Получении профиля всего OpenMP приложения
- Профиле отдельных параллельных регионов OpenMP (время CPU, горячие функции и т.д.)
- Балансе работы внутри отдельного параллельного региона OpenMP
- Балансе параллельного/последовательного кода
- Уровне гранулярности параллельных задач
- Объектах синхронизации, времени ожидания и передачах управления между потоками
Запуск профилировки OpenMP приложения
Для профилировки OpenMP программы вам понадобится VTune Amplifier XE 2013 Update 4 или новее. Приложение лучше строить Composer XE 2013 Update 2 или новее. Анализ более старых реализаций OpenMP от Intel или от других производителей (GCC и Microsoft OpenMP) также возможен, но полезной информации будет собрано меньше, т.к. VTune Amplifier XE не сможет распознать их параллельные регионы.
Все описываемые в статье шаги справедливы для Windows и Linux. Приведённые примеры тестировались на Linux.
Если у вас компилятор старше, чем из пакета Intel Composer XE 2013 SP1, нужно выставить переменную окружения KMP_FORKJOIN_FRAMES в 1. Это можно сделать в самом VTune Amplifier в диалоге «User-defined Environment Variables» в свойствах проекта, ну или вручную:
# export KMP_FORKJOIN_FRAMES=1
Чтобы получить полную информацию об исходных файлах с параллельными регионами, компилируйте с опцией -parallel-source-info=2. Для своих примеров я использовал такую строку компиляции:
# icc -openmp -O3 -g -parallel-source-info=2 omptest.cpp work.cpp -o omptest
Всё остальное не отличается от анализа обычного приложения: запускаем VTune Amplifier, создаём проект, указываем наше приложение и запускаем профилировку:
Представление параллельных регионов OpenMP
Параллельные регионы OpenMP представлены в VTune Amplifier XE как фрейм домены (Frame domains). Фреймы – это последовательность непересекающихся промежутков времени исполнения приложения. Т.е. можно разбить всё время работы программы на стадии: например, стадия 1 (инициализация), стадия 2 (работа), стадия 3 (завершение). Эти стадии могут быть представлены тремя фреймами. Фреймы часто упоминаются в графических приложениях – идея та же, но понятие фрейма в VTune Amplifier шире. Фреймы глобальны и не привязаны к конкретным потокам.
Каждый параллельный регион OpenMP показан как отдельный фрейм домен. Он идентифицируется исходным файлом и номерами строк. Фрейм домен обозначает регион в исходном коде. Каждый вызов этого региона — это фрейм. Фрейм – период от точки разделения (или запуска) потоков (fork) до точки их воссоединения (join). Количество фреймов не связано с количеством потоков и размером задач.
Псевдо код ниже содержит две параллельные конструкции OpenMP, два региона. Каждый из них будет распознан как фрейм домен в профиле VTune Amplifier XE, так что будет два фрейм домена:
int main()
{
#pragma omp parallel for // frame domain #1, frame count: 1
for (int i=0; i < NUM_ITERATIONS; i++)
{
do_work();
}
for (int j=0; j<4; j++)
{
#pragma omp parallel for // frame domain #2, frame count: 4
for (int i=0; i < NUM_ITERATIONS; i++)
{
do_work();
}
}
}
Первый фрейм домен вызывается только один раз. Поэтому frame domain #1 будет иметь только 1 фрейм, даже если тело параллельного цикла исполняется сразу 16-ю потоками. Второй параллельный регион (frame domain #2) запускается из цикла последовательно 4 раза. Для каждой итерации вызывается параллельная конструкция, с соответствующими запусками и завершениями потоков. Поэтому frame domain #2 будет иметь 4 фрейма в профиле VTune Amplifier XE.
Параллельные регионы распознаются, если программа использует Intel OpenMP рантайм. Чтобы их увидеть, в результате VTune Amplifier XE переключитесь на вкладку Bottom-up и выберите группировку по «Frame Domain /Frame Type …»:
Параллельные регионы OpenMP и соответствующую им активность потоков можно также наблюдать во вкладке Tasks and Frames:
Последовательный регион
Всё время CPU, потраченное вне параллельных регионов, собирается во фрейм домен с названием “[No frame domain — Outside any frame]”. Это позволяет вам оценить последовательную часть вашего кода:
Накладные расходы и активное ожидание (overhead and spin time)
Накладные расходы (Overhead time) в OpenMP – это время, проведённое в исполнении внутренних процедур рантайма, связанных с управлением потоками, распределением работы, диспетчеризацией, синхронизацией и т.п. Это время, потраченное не на полезные вычисления, а на внутренние функции библиотеки. Время активного ожидания (Spin time) – время ожидания, во время которого CPU работает. Это может происходить, например, если объект синхронизации делает вызов poll, вместо того, чтобы уходить в состояние ожидания – «крутится» («spinning»), пока ждёт. Потоки OpenMP могут тоже так «крутиться», например, на барьере синхронизации.
Накладные расходы и активное ожидание отлавливаются по известным именам функций и последовательностям вызовов, которые тратят время CPU. Некоторые внутренние функции OpenMP управляют потоками, задачами, и т.п., поэтому время, проведённое в них, относится к накладным расходам. Так же определяется и время активного ожидания, по функциям, реализующим «кручение».
Накладные расходы и активное ожидание определяются для рантаймов Intel OpenMP, GCC и Microsoft OpenMP, Intel Threading Building Blocks и Intel Cilk Plus.
Сценарий 1: Идеально сбалансированный параллельный регион
В моём простом примере параллельный регион в файле omptest.cpp на строке 54 – хороший случай. Посмотрите на вкладку Bottom-up, группировка по “Frame Domain/Frame Type/Frame/Thread/Function/Call Stack”:
Фрейм домен содержит только один фрейм, что означает, что параллельный регион был вызван только один раз. Раскрыв детали в таблице видно 8 потоков. Это хорошо для 4-х ядерной машины с Hyper Threading, на которой проводился тест. CPU хорошо загружен (зелёный цвет полоски CPU time), все 8 потоков заняты в этом регионе и выполняют почти одинаковое количество работы. Этот параллельный регион отмечен на временной шкале, где также видна высокая загрузка процессора для всех восьми потоков. Это не означает, что всё идеально – например, могут быть промахи кэша или недостаточное использование SIMD инструкций. Но никаких проблем с потоками OpenMP и балансом работы не обнаружено.
Код из примера на строке 54:
#pragma omp parallel for schedule(static,1) // line 54
for (int index = 0 ; index < oloops ; index++)
{
double *a, *b, *c, *at ;
int ick ;
a = ga + index*84 ;
c = gc + index*84 ;
fillmat (a) ;
ick = work (a, c,gmask) ;
if (ick > 0)
{
printf("error ick failedn") ;
exit(1) ;
}
}
Сценарий 2: Несбалансированный параллельный регион
Регион на строке 82 – не такой сбалансированный. Он использует лишь 4 потока из 8 доступных, остальные 4 ждут. Это отражено и в уровне загрузки процессора (красный цвет):
Код на строке 82 (просто выключили каждую вторую итерацию):
#pragma omp parallel for schedule(static,1) // line 82
for (int index = 0 ; index < oloops ; index++)
{
double *a, *b, *c, *at ;
int ick ;
if (index%2 == 0)
{
a = ga + index*84 ;
c = gc + index*84 ;
fillmat (a) ;
ick = work (a, c, gmask) ;
if (ick > 0)
{
printf("error ick failedn") ;
exit(1) ;
}
}
}
Сценарий 3: Проблемы с гранулярностью
Предыдущие примеры имели один фрейм домен и один фрейм. Регион на строке 147 содержит множество фреймов:
Это означает, что параллельный регион вызывался множество раз. Время CPU каждого фрейма очень мало – это можно видеть и во всплывающем окне, когда наводите мышкой на фрейм во временной шкале. Это наводит на мысль, что гранулярность слишком высока, в том смысле, что мы слишком часто запускаем очень короткие параллельные OpenMP регионы. От этого мы получаем большие накладные расходы и низкую загрузку CPU.
Код на строке 147:
for (q = 0 ; q < LOOPS ; q++)
{
#pragma omp parallel for schedule(static,1) firstprivate(tcorrect) lastprivate(tcorrect) // line 147
for (int index = 0 ; index < oloops ; index++)
{
double *la, *lc;
int lq,lmask ;
la = ga + index*84 ;
lc = gc + index*84 ;
lq = q ;
lmask = gmask ;
ick = work1(ga, gc, lq,lmask) ;
if (ick == VLEN) tcorrect++ ;
}
}
Сценарий 4: Объекты синхронизации и время ожидания
Ожидание на объекте синхронизации может быть серьёзным узким местом для производительности. Для получения полной картины синхронизаций и ожиданий в вашем приложении, соберите анализ “Locks and Waits”. Перед запуском включите галки “Analyze user tasks” и “Analyze Intel runtimes and user synchronization” в настройках нового анализа.
Панель Bottom-up покажет вам список объектов синхронизации, отсортированных по времени ожидания:
VTune Amplifier XE может распознавать примитивы синхронизации OpenMP, такие как конструкция “omp critical” или барьеры синхронизации, используемые внутри OpenMP рантайма. Вы можете увидеть, сколько времени потрачено в ожидании и как оно распределено: много ожиданий короткой продолжительности, или несколько длительных ожиданий. VTune Amplifier XE показывает, ждал ли поток активно (spin waiting), или действительно ушёл в состояние ожидания. Временная шкала даёт картину переходов управления (transitions) – вертикальные жёлтые линии. По ним вы можете понять, какие потоки перехватывали объект синхронизации, как часто, какой именно это был объект, сколько времени они ждали, и т.п.
Код на строке 118:
#pragma omp parallel for schedule(static,1)
for (int index = 0 ; index < oloops ; index++)
{
#pragma omp critical (my_sync) // line 118
{
double *a, *b, *c, *at ;
int ick ;
a = ga + index*84 ;
c = gc + index*84 ;
fillmat (a) ;
ick = work (a, c,gmask) ;
if (ick > 0)
{
printf("error ick failedn") ;
exit(1) ;
}
}
}
Резюме
Intel VTune Amplifier XE даёт возможность взглянуть глубоко внутрь OpenMP приложения. Вы можете оценить баланс последовательного и параллельного кода, и как программа ведёт себя в каждом параллельном регионе. Intel VTune Amplifier XE может помочь вам найти проблемы с балансом нагрузки между потоками OpenMP, проблемы с гранулярностью, оценить накладные расходы и понять схему синхронизации. Привязка детальной статистики использования процессора к конкретному региону OpenMP позволит вам лучше понять поведение вашего приложения. Наиболее детальную информацию вы можете получить, используя Intel OpenMP рантайм, но профилировка других реализаций тоже возможна (GCC и Microsoft OpenMP).
Автор: krogozh