OpenMP卸载到Nvidia错误的减少

Z b*_*son 7 c++ gcc openmp offloading openacc

我有兴趣使用OpenMP将工作卸载到GPU.

下面的代码给出sum了CPU 的正确值

//g++ -O3 -Wall foo.cpp -fopenmp
#pragma omp parallel for reduction(+:sum)                                                                                                                                    
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
Run Code Online (Sandbox Code Playgroud)

它也适用于像OpenACC这样的GPU

//g++ -O3 -Wall foo.cpp -fopenacc   
#pragma acc parallel loop reduction(+:sum)                                                                                                                                    
for(int i = 0 ; i < 2000000000; i++) sum += i%11;
Run Code Online (Sandbox Code Playgroud)

nvprof 表明它在GPU上运行,并且它也比CPU上的OpenMP更快.

但是,当我尝试使用这样的OpenMP卸载到GPU时

//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;
Run Code Online (Sandbox Code Playgroud)

它得到了错误的结果sum(它只返回零).nvprof似乎表明它在GPU上运行,但它比CPU上的OpenMP慢得多.

为什么GPU上的OpenMP减少失败?

这是我用来测试它的完整代码

#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 = %d\n",sum);
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用GCC 7.2.0,Ubuntu 17.10,以及gcc-offload-nvptx

Z b*_*son 3

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;
Run Code Online (Sandbox Code Playgroud)

这得到了正确的结果,sum但是代码仍然比使用不带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;
Run Code Online (Sandbox Code Playgroud)

另一种选择是使用defaultmap(tofrom:scalar)

#pragma omp target teams distribute parallel for reduction(+:sum) defaultmap(tofrom:scalar)
Run Code Online (Sandbox Code Playgroud)

显然,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;
}
Run Code Online (Sandbox Code Playgroud)

nvprof表明大部分时间都花在了cuCtxSynchronize。使用 OpenACC 的话,这个数字大约是一半。


我终于设法显着加快了减少速度。解决方案是添加simd条款

#pragma omp target teams distribute parallel for simd reduction(+:sum) map(tofrom:sum).
Run Code Online (Sandbox Code Playgroud)

这是一行九个子句。一个稍微短一点的解决方案是

#pragma omp target map(tofrom:sum)
#pragma omp teams distribute parallel for simd reduction(+:sum)
Run Code Online (Sandbox Code Playgroud)

时间是

OMP_GPU    0.25 s
ACC        0.47 s
OMP_CPU    0.64 s
Run Code Online (Sandbox Code Playgroud)

现在,GPU 上的 OpenMP 比 CPU 上的 OpenACC 和 OpenMP 快得多。我不知道 OpenACC 是否可以通过一些附加条款来加速。

希望 Ubuntu 18.04 能够修复,gcc-offload-nvptx这样它就不再需要-fno-stack-protector.