Выгрузка OpenMP на Nvidia неправильное сокращение
меня интересует разгрузка работы на GPU с OpenMP.
код ниже дает правильное значение sum на CPU
//g++ -O3 -Wall foo.cpp -fopenmp
#pragma omp parallel for reduction(+:sum)                                                                                                                                    
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
он также работает на GPU с OpenACC, как это
//g++ -O3 -Wall foo.cpp -fopenacc   
#pragma acc parallel loop reduction(+:sum)                                                                                                                                    
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
nvprof показывает, что он работает на GPU, а также быстрее, чем OpenMP на CPU.
однако, когда я пытаюсь разгрузить GPU с OpenMP, как это
//g++ -O3 -Wall foo.cpp -fopenmp -fno-stack-protector
#pragma omp target teams distribute parallel for reduction(+:sum)
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
он получает неправильный результат для sum (Он просто возвращает нуль.) nvprof кажется, показывает, что он работает на GPU, но он намного медленнее, чем OpenMP на CPU.
почему сокращение не удается с OpenMP на GPU?
вот полный код, который я использовал для проверки этого
#include <stdio.h>
//g++ -O3 -Wall acc2.cpp -fopenmp -fno-stack-protector                                                                                                                           
//sudo nvprof ./a.out                                                                                                                                                            
int main (void) {
  int sum = 0;
  //#pragma omp parallel for reduction(+:sum)                                                                                                                                    
  //#pragma acc parallel loop reduction(+:sum)                                                                                                                                   
  #pragma omp target teams distribute parallel for reduction(+:sum)
  for(int i = 0 ; i < 2000000000; i++) {
    sum += i%11;
  }
  printf("sum = %dn",sum);
  return 0;
}
использование GCC 7.2.0, Ubuntu 17.10, вместе с GCC-offload-nvptx
1 ответов
решение было добавить пункт map(tofrom:sum) такой:
//g++ -O3 -Wall foo.cpp -fopenmp -fno-stack-protector
#pragma omp target teams distribute parallel for reduction(+:sum) map(tofrom:sum)
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
это получает правильный результат для sum однако код все еще намного медленнее, чем с OpenACC или OpenMP без target.  
обновление: решение скорости состояло в том, чтобы добавить simd предложения. Дополнительные сведения см. В конце этого ответа.
решение выше имеет много предложений в одной строке. Это может быть разбито, как это:
#pragma omp target data map(tofrom: sum)
#pragma omp target teams distribute parallel for reduction(+:sum)
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
другой вариант-использовать defaultmap(tofrom:scalar)
#pragma omp target teams distribute parallel for reduction(+:sum) defaultmap(tofrom:scalar)
по-видимому, скалярные переменные в OpenMP 4.5 являются firstprivate по умолчанию.
https://developers.redhat.com/blog/2016/03/22/what-is-new-in-openmp-4-5-3/
defaultmap(tofrom:scalar) удобно, если у вас есть несколько скалярных значений, которые вы хотите поделиться.
Я также реализовал сокращение вручную, чтобы увидеть, могу ли я ускорить его. Мне не удалось ускорить это, но вот код в любом случае (есть другие оптимизации, которые я пробовал, но никто из них не помог).
#include <omp.h>
#include <stdio.h>
//g++ -O3 -Wall acc2.cpp -fopenmp -fno-stack-protector
//sudo nvprof ./a.out
static inline int foo(int a, int b, int c) {
  return a > b ? (a/c)*b + (a%c)*b/c : (b/c)*a + (b%c)*a/c;
}
int main (void) {
  int nteams = 0, nthreads = 0;
  #pragma omp target teams map(tofrom: nteams) map(tofrom:nthreads)
  {
    nteams = omp_get_num_teams();
    #pragma omp parallel
    #pragma omp single
    nthreads = omp_get_num_threads();
  }
  int N = 2000000000;
  int sum = 0;
  #pragma omp declare target(foo)  
  #pragma omp target teams map(tofrom: sum)
  {
    int nteams = omp_get_num_teams();
    int iteam = omp_get_team_num();
    int start  = foo(iteam+0, N, nteams);
    int finish = foo(iteam+1, N, nteams);    
    int n2 = finish - start;
    #pragma omp parallel
    {
      int sum_team = 0;
      int ithread = omp_get_thread_num();
      int nthreads = omp_get_num_threads();
      int start2  = foo(ithread+0, n2, nthreads) + start;
      int finish2 = foo(ithread+1, n2, nthreads) + start;
      for(int i=start2; i<finish2; i++) sum_team += i%11;
      #pragma omp atomic
      sum += sum_team;
    }   
  }   
  printf("devices %d\n", omp_get_num_devices());
  printf("default device %d\n", omp_get_default_device());
  printf("device id %d\n", omp_get_initial_device());
  printf("nteams %d\n", nteams);
  printf("nthreads per team %d\n", nthreads);
  printf("total threads %d\n", nteams*nthreads);
  printf("sum %d\n", sum);
  return 0;
}
nvprof показывает, что большую часть времени провожу с cuCtxSynchronize. С OpenACC это примерно половина.
мне, наконец, удалось резко ускорить снижение. Решение было добавить simd п.
#pragma omp target teams distribute parallel for simd reduction(+:sum) map(tofrom:sum).
это девять пунктов в одной строке. Немного более короткое решение -
#pragma omp target map(tofrom:sum)
#pragma omp teams distribute parallel for simd reduction(+:sum)
в времена
OMP_GPU    0.25 s
ACC        0.47 s
OMP_CPU    0.64 s
OpenMP на GPU теперь намного быстрее, чем OpenACC и OpenMP на CPU . Я не знаю, можно ли ускорить OpenACC с некоторыми дополнительными предложениями.
надеюсь, Ubuntu 18.04 исправляет gcc-offload-nvptx так что не надо -fno-stack-protector.