Профилировка производительности OpenMP приложений

в 5:24, , рубрики: VTune Amplifier XE, Блог компании Intel, высокая производительность, параллельное программирование, метки:

Профилировка производительности OpenMP приложений

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

Параллельные регионы 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 приложений

Параллельные регионы OpenMP и соответствующую им активность потоков можно также наблюдать во вкладке Tasks and Frames:

Профилировка производительности OpenMP приложений

Последовательный регион

Всё время CPU, потраченное вне параллельных регионов, собирается во фрейм домен с названием “[No frame domain — Outside any frame]”. Это позволяет вам оценить последовательную часть вашего кода:

Профилировка производительности OpenMP приложений

Накладные расходы и активное ожидание (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.

Профилировка производительности OpenMP приложений

Сценарий 1: Идеально сбалансированный параллельный регион

В моём простом примере параллельный регион в файле omptest.cpp на строке 54 – хороший случай. Посмотрите на вкладку Bottom-up, группировка по “Frame Domain/Frame Type/Frame/Thread/Function/Call Stack”:

Профилировка производительности OpenMP приложений

Фрейм домен содержит только один фрейм, что означает, что параллельный регион был вызван только один раз. Раскрыв детали в таблице видно 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 ждут. Это отражено и в уровне загрузки процессора (красный цвет):

Профилировка производительности OpenMP приложений

Код на строке 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 содержит множество фреймов:

Профилировка производительности OpenMP приложений

Это означает, что параллельный регион вызывался множество раз. Время 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 покажет вам список объектов синхронизации, отсортированных по времени ожидания:

Профилировка производительности OpenMP приложений

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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js