Почему я получаю худшую производительность с частным динамическим массивом

#c #c #multithreading #performance #openmp

#c #c #многопоточность #Производительность #openmp

Вопрос:

Я хочу использовать OpenMP для распараллеливания калькулятора for-loop, который выполняет что-то вроде:

 B = (int*)malloc(sizeof(int) * N); //N is known
for(i=0;i<500000;i  )
{  
    for(j=0;j<M;j  ) B[j]=i j;  //M is different from N, but M <= N;
    some operations on B which produce a variable L;
    printf("%dn",L);    
}
 

Мне не нужно перераспределять B, поскольку его значения будут определены для каждой итерации соответственно. Операции будут использовать только от B [0] до B[M-1]. Это экономит много времени при выделении и инициализации B.

Чтобы использовать openmp, я изменил код на это:

 #pragma omp parallel num_threads(32) private(i,j,B,M,L)
{
  B = (int*)malloc(sizeof(int) * N); //N is known
  #pragma omp parallel for 
  for(i=0;i<500000;i  )
  {  
      for(j=0;j<M;j  ) B[j]=i j;  //M is different from N, but M <= N;
      some operations on B which produce a variable L;
      printf("%dn",L);    
  }
}
 

Он работает очень медленно по сравнению с первым, поскольку создает новый массив B для каждого потока (так 500000 раз).
Есть ли способ избежать этого с помощью openmp?

Ответ №1:

Основная проблема заключается в том, что итерации цикла не назначаются потокам, как вы хотели. Поскольку вы снова добавили предложение parallel в #pragma omp for , и предполагая, что у вас отключен вложенный параллелизм, который по умолчанию отключен, каждый из потоков, созданных во внешней parallel области, будет выполнять «последовательно» код в этой области, а именно:

   #pragma omp parallel for 
  for(i=0;i<500000;i  ){  
      ...
  }
 

Следовательно, каждый поток будет выполнять все 500000 итерации внутреннего цикла, которые вы намеревались распараллелить. Следовательно, удаление параллелизма и добавление дополнительных накладных расходов (например, создание потоков) к последовательному коду. Тем не менее, можно легко решить эту проблему, просто удалив второе parallel предложение, а именно:

 #pragma omp parallel num_threads(32) private(i,j,B,M,L)
{
    B = (int*)malloc(sizeof(int) * N); //N is known
    #pragma omp for 
    for(i=0;i<500000;i  ){  
      ...   
    }
}
 

В зависимости от настройки, в которой будет выполняться код (например, в NUMA архитектуре или нет, является ли malloc используемая функция (или нет) потоковым распределителем памяти, среди прочего), может быть целесообразно профилировать вашу параллельную область, чтобы проверить, окупается (или нет) перемещение выделениямассива 2D за пределы этой области. Пример того, как может выглядеть альтернативная версия:

 int total_threads = 32;
int** B = malloc(sizeof(*int) * total_threads);
for(int i = 0; i < total_threads; i  ){
    B[i] = malloc(N * sizeof(int));
}

#pragma omp parallel num_threads(32) private(i,j,M,L)
{
  int threadID = omp_get_thread_num();
  #pragma omp for 
  for(i=0;i<500000;i  )
  {  
      for(j=0;j<M;j  ) 
          B[threadID][j]=i j;  //M is different from N, but M <= N;
      some operations on B which produce a variable L;
      printf("%dn",L);    
  }
}
// you might need to reduce all the values from all threads
// to main thread array.