Openmp вложенный цикл - PullRequest
       13

Openmp вложенный цикл

2 голосов
/ 22 августа 2011

просто играю с openmp. Посмотрите на фрагменты этого кода:

#pragma omp parallel
{
    for( i =0;i<n;i++)
    {
        doing something
    }
}

и

for( i =0;i<n;i++)
{
  #pragma omp parallel
  {
     doing something
  }
}

Почему первый намного медленнее (примерно в 5 раз), чем второй? Из теории я думал, что первый должен быть быстрее, потому что параллельная область создается только один раз, а не n раз, как второй? Может кто-нибудь объяснить мне это?

Код, который я хочу распараллелить, имеет следующую структуру:

for(i=0;i<n;i++) //wont be parallelizable
{
  for(j=i+1;j<n;j++)  //will be parallelized
  {
    doing sth.
  }

  for(j=i+1;j<n;j++)  //will be parallelized
    for(k = i+1;k<n;k++)
    {
      doing sth.
    }

}

Я сделал простую программу для измерения времени и воспроизведения результатов.

#include <stdio.h>
#include <omp.h>

void test( int n)
{
  int i ;
  double t_a = 0.0, t_b = 0.0 ;


  t_a = omp_get_wtime() ;

  #pragma omp parallel
  {
    for(i=0;i<n;i++)
    {

    }
  }

  t_b = omp_get_wtime() ;

  for(i=0;i<n;i++)
  {
    #pragma omp parallel
    {
    }
  }

  printf( "directive outside for-loop: %lf\n", 1000*(omp_get_wtime()-t_a)) ;
  printf( "directive inside for-loop: %lf \n", 1000*(omp_get_wtime()-t_b)) ;
}

int main(void)
{
  int i, n   ;
  double t_1 = 0.0, t_2 = 0.0 ;

  printf( "n: " ) ;
  scanf( "%d", &n ) ;

  t_1 = omp_get_wtime() ;

  #pragma omp parallel
  {
    for(i=0;i<n;i++)
    {

    }
  }

  t_2 = omp_get_wtime() ;

  for(i=0;i<n;i++)
  {
    #pragma omp parallel
    {
    }
  }

  printf( "directive outside for-loop: %lf\n", 1000*(omp_get_wtime()-t_1)) ;
  printf( "directive inside for-loop: %lf \n", 1000*(omp_get_wtime()-t_2)) ;

  test(n) ;

  return 0 ;
}

Если я начну с разных n, я всегда получаю разные результаты.

n: 30000
directive outside for-loop: 0.881884
directive inside for-loop: 0.073054 
directive outside for-loop: 0.049098
directive inside for-loop: 0.011663 

n: 30000
directive outside for-loop: 0.402774
directive inside for-loop: 0.071588 
directive outside for-loop: 0.049168
directive inside for-loop: 0.012013 

n: 30000
directive outside for-loop: 2.198740
directive inside for-loop: 0.065301 
directive outside for-loop: 0.047911
directive inside for-loop: 0.012152 



n: 1000
directive outside for-loop: 0.355841
directive inside for-loop: 0.079480 
directive outside for-loop: 0.013549
directive inside for-loop: 0.012362 

n: 10000
directive outside for-loop: 0.926234
directive inside for-loop: 0.071098 
directive outside for-loop: 0.023536
directive inside for-loop: 0.012222 

n: 10000
directive outside for-loop: 0.354025
directive inside for-loop: 0.073542 
directive outside for-loop: 0.023607
directive inside for-loop: 0.012292 

Как вы можете объяснить мне эту разницу ?!

Результаты с вашей версией:

Input n: 1000
[2] directive outside for-loop: 0.331396
[2] directive inside for-loop: 0.002864 
[2] directive outside for-loop: 0.011663
[2] directive inside for-loop: 0.001188 
[1] directive outside for-loop: 0.021092
[1] directive inside for-loop: 0.001327 
[1] directive outside for-loop: 0.005238
[1] directive inside for-loop: 0.001048 
[0] directive outside for-loop: 0.020812
[0] directive inside for-loop: 0.001188 
[0] directive outside for-loop: 0.005029
[0] directive inside for-loop: 0.001257 

Ответы [ 2 ]

4 голосов
/ 22 августа 2011

потому что параллельная область создается только один раз, а не n раз, как вторая?

Вид. Конструкция

#pragma omp parallel
{
}

также означает выделение рабочих элементов потокам в '{' и возврат потоков в пул потоков в '}'. В нем много межпоточных коммуникаций. Кроме того, по умолчанию ожидающие потоки перейдут в спящий режим через ОС, и для пробуждения потока потребуется некоторое время.

О вашем среднем примере: вы можете попытаться ограничить внешнюю for параллельность с ...

#pragma omp parallel private(i,k)
{
for(i=0;i<n;i++) //w'ont be parallelized
{
  #pragma omp for
  for(j=i+1;j<n,j++)  //will be parallelized
  {
    doing sth.
  }
  #pragma omp for    
  for(j=i+1;j<n;j++)  //will be parallelized
    for(k = i+1;k<n;k++)
    {
      doing sth.
    }
  // Is there really nothing? - if no - use:
  // won't be parallelized
  #pragma omp single
  { //seq part of outer loop
      printf("Progress... %i\n", i); fflush(stdout);
  }

  // here is the point. Every thread did parallel run of outer loop, but...
  #pramga omp barrier

  //  all loop iterations are syncronized:
  //       thr0   thr1  thr2
  // i      0      0     0
  //     ----   barrier ----
  // i      1      1     1
  //     ----   barrier ----
  // i      2      2     2
  // and so on
}
}

Как правило, размещение параллели на максимально возможном (верхнем) for из for гнезда лучше, чем размещение его на внутренних циклах. Если вам нужно последовательное выполнение некоторого кода, используйте для этого расширенные прагмы (например, omp barrier, omp master или omp single) или omp_locks. Любой из этих способов будет быстрее, чем запуск omp parallel много раз

2 голосов
/ 24 августа 2011

Ваш полный тест очень неправильный. Вы посчитали время для обеих частей кода и для второй; не время первого раздела. Кроме того, вторая строка printf измерила время первого printf.

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

Моя версия вашего теста:

$ cat test.c
#include <stdio.h>
#include <omp.h>

void test( int n, int j)
{
  int i ;
  double t_a = 0.0, t_b = 0.0, t_c = 0.0 ;
  t_a = omp_get_wtime() ;
  #pragma omp parallel
  {
    for(i=0;i<n;i++) { }
  }
  t_b = omp_get_wtime() ;
  for(i=0;i<n;i++) {
    #pragma omp parallel
    { }
  }
  t_c = omp_get_wtime() ;
  printf( "[%i] directive outside for-loop: %lf\n", j, 1000*(t_b-t_a)) ;
  printf( "[%i] directive inside for-loop: %lf \n", j, 1000*(t_c-t_b)) ;
}

int main(void)
{
  int i, n, j=3  ;
  double t_1 = 0.0, t_2 = 0.0, t_3 = 0.0;
  printf( "Input n: " ) ;
  scanf( "%d", &n ) ;
  while( j --> 0 ) {
      t_1 = omp_get_wtime();
      #pragma omp parallel
      {
        for(i=0;i<n;i++) { }
      }

      t_2 = omp_get_wtime();

      for(i=0;i<n;i++) {
        #pragma omp parallel
        { }
      }
      t_3 = omp_get_wtime();
      printf( "[%i] directive outside for-loop: %lf\n", j, 1000*(t_2-t_1)) ;
      printf( "[%i] directive inside for-loop: %lf \n", j, 1000*(t_3-t_2)) ;
      test(n,j) ;
  }
  return 0 ;
}

Я сделал 3 прогона для каждого n внутри самой программы.

Результаты:

$ ./test
Input n: 1000
[2] directive outside for-loop: 5.044824
[2] directive inside for-loop: 48.605116
[2] directive outside for-loop: 0.115031
[2] directive inside for-loop: 1.469195
[1] directive outside for-loop: 0.082415
[1] directive inside for-loop: 1.455855
[1] directive outside for-loop: 0.081297
[1] directive inside for-loop: 1.462352
[0] directive outside for-loop: 0.080528
[0] directive inside for-loop: 1.455786
[0] directive outside for-loop: 0.080807
[0] directive inside for-loop: 1.467101

Затрагивается только первый запуск test(). Все следующие результаты одинаковы для test и main().

Более качественные и стабильные результаты получены при таком запуске (я использовал gcc-4.6.1 и статическую сборку)

$ OMP_WAIT_POLICY=active GOMP_CPU_AFFINITY=0-15 OMP_NUM_THREADS=2  ./test
Input n: 5000
[2] directive outside for-loop: 0.079412
[2] directive inside for-loop: 4.266087
[2] directive outside for-loop: 0.031708
[2] directive inside for-loop: 4.319727
[1] directive outside for-loop: 0.047563
[1] directive inside for-loop: 4.290812
[1] directive outside for-loop: 0.033733
[1] directive inside for-loop: 4.324406
[0] directive outside for-loop: 0.047004
[0] directive inside for-loop: 4.273143
[0] directive outside for-loop: 0.092331
[0] directive inside for-loop: 4.279219

Я установил две переменные среды производительности omp и ограничил число потоков до 2.

Также. У вас "параллельный" цикл неправильный. (и я воспроизвел эту ошибку в моем варианте ^^^) Переменная i находится здесь:

      #pragma omp parallel
      {
        for(i=0;i<n;i++) { }
      }

Вы должны иметь это как

      #pragma omp parallel
      {
        for(int local_i=0;local_i<n;local_i++) { }
      }

ОБНОВЛЕНИЕ7 Ваш результат для n = 1000:

[2] directive inside for-loop: 0.001188 
[1] directive outside for-loop: 0.021092
[1] directive inside for-loop: 0.001327 
[1] directive outside for-loop: 0.005238
[1] directive inside for-loop: 0.001048 
[0] directive outside for-loop: 0.020812
[0] directive inside for-loop: 0.001188 
[0] directive outside for-loop: 0.005029
[0] directive inside for-loop: 0.001257 

0,001 или 0,02 для вашего кода - это .... секунды, умноженные на 1000, поэтому это миллисекунда (мс). И это ... около 1 микросекунды или 20 микросекунд. Гранулярность некоторых системных часов (user time или system time выходных полей утилиты time) составляет от 1 миллисекунды, 3 мс или 10 мс. 1 микросекунда - 2000-3000 тактов процессора (для процессора 2-3 ГГц). Таким образом, вы не можете измерить такой короткий интервал времени без специальной настройки. Вы должны:

  1. отключить энергосбережение ЦП (Intel SpeedStep, AMD ???), которое может перевести ЦП в режим пониженного энергопотребления путем понижения его тактовой частоты (частоты);
  2. отключить динамический разгон процессора (Intel TurboStep);
  3. Измерять время без помощи ОС, например, читая TSC (rdtsc asm инструкция)
  4. Отключить переупорядочение команд на ЦП вне очереди (только атом не является процессором текущего поколения) до и после rdtsc, добавив инструкцию cpuid (или другую инструкцию, которая отключит переупорядочение)
  5. Выполните запуск на полностью бесплатной системе (загрузка процессора 0% на оба процессора перед началом теста)
  6. переписать тест неинтерактивным способом (не ждите ввода от пользователя с помощью scanf, передайте n через argv[1])
  7. Не используйте Xserver и медленный терминал для вывода результатов
  8. Уменьшить число прерываний (физически отключить сеть; не воспроизводить фильм в фоновом режиме, не касаться мыши и клавиатуры)
  9. Выполнить много прогонов (я имею в виду не перезапуск программы, а перезапуск измеренной части программы; в моей программе j = 100) и добавить статистический расчет по результатам.
  10. Не запускайте printf так часто (между измерениями); это загрязнит тайники и TLB. Сохраняйте результаты внутри и выводите их после всех измерений.

ОБНОВЛЕНИЕ 8: В качестве статистики я имею в виду: принять несколько значений, 7 или более. Откажитесь от первого значения (или даже 2-3 первых значений, если у вас есть большое количество измеренных значений). Сортировать их. Откажитесь ... 10-20% от максимальных и минимальных результатов. Рассчитать среднее. Буквально

double results[100], sum=0.0, mean = 0.0;
int count = 0;
// sort results[5]..results[100] here
for(it=20; it< 85; it ++) {
  count++; sum+= results[it];
}
mean = sum/count;
...