OpenMP 相关技巧

面向英特尔® MIC 架构的编译器方法

OpenMP 相关技巧

OpenMP* 循环崩溃指令(Loop Collapse Directive

使用 OpenMP 崩溃语句来增加将在可用 OMP 线程上进行分区的迭代的总数通过降低每条线程所开展的工作的粒度。如果每条线程所执行的工作是比较重要的(应用崩溃之后),这将有助于提高 OMP 应用的并行扩展能力。

通过避免使用崩溃循环嵌套中的崩溃循环索引如果可能),您可以显著提高性能因为编译器必须使用 divide/mod划分/修改操作从崩溃循环索引进行重建而且整个过程十分复杂因此对编译器进行优化不能消除死代码

以下为使用崩溃语句的示例:

#pragma omp parallel for collapse(2)

  for (i = 0; i < imax; i++) {

    for (j = 0; j < jmax; j++) a[ j + jmax*i] = 1.;

  }

可提高性能的修改示例

k=0;

#pragma omp parallel for collapse(2)

  for (i = 0; i < imax; i++) {

     for (j = 0; j < jmax; j++) a[ k++] = 1.;

  }

OpenMP 语句的常见错误

请确保添加与数据共享相关的正确 OpenMP 语句使用关键字private, reduction 。如果没有按照要求使用,程序可能会发生数据竞争。但是,由于数据竞争的不确定效果,在某些配置中表面上可能工作“正常”(与正确修改的版本相比,性能特征具有很大的不同)。

请见下面的示例

带有数据竞争的错误代码

      #pragma omp parallel for
      for(i=0; i<threads; i++)
      {
            offset=i*array_size;
            for(j=0; j<(iterations*(vec_ratio)); j++)
            {
                  for(k=0; k<array_size; k++) 
                  {
                        sum1 += a[k+offset] * s1;
                        sum2 += a[k+offset] * s2;
                  }
            }
      }

消除数据竞争的修改版本

  #pragma omp parallel for num_threads(threads) reduction(+:sum1,sum2)
  for(i=0; i<threads; i++)
  {
    float sum1_local=0.0f;
    float sum2_local=0.0f;
    int offset=i*array_size;

    for(int j=0; j<(iterations*(vec_ratio)); j++)
    {
      for(int k=0; k<array_size;  k++)
      {
        sum1_local += a[k+offset] * s1;
        sum2_local += a[k+offset] * s2;
      }
    }
    sum1 += sum1_local;
    sum2 += sum2_local;
  }

减少 barrier 同步开销

取决于所使用的 OpenMP 结构在大量线程上执行 barrier 同步会显著增加开销。在某些情况下,用户可使用 “nowait” 语句来减少此类开销。在下面的示例中,该语句与静态调度结合使用,以便针对并行区内部的循环使用 omp-。该语句的含义是,完成每个单独的工作并不能实现线程的同步。请注意,此处可以使用 “nowait” 语句和静态调度,因为相同的线程可在每个循环中执行相同的迭代数量 一个线程总是针对不同 image_id 值执行相同的内部循环迭代。(将调度类型改为动态会使 “nowait” 语句发生错误)。

void *task(void* tid_, float **Raw, float *Vol)
{
  long int tid = (long int) tid_;
  if(tid==0) printf("bptask_vgather\n");
  int _kk_;
  int il,jl,kl,i,j,k,kbase;
  __int64 cycles=0;

  #pragma omp parallel
  for(int image_id =0; image_id < global_number_of_projection_images; image_id++) {
    float *local_Raw = Raw[image_id];
    int chunksize = NBLOCKS/nthreads; // nthreads==1, tid==0
    int beg_ = tid*chunksize;
    int end_ = (tid+1)*chunksize;

    #pragma omp for schedule(static) nowait
    for(int b=beg_; b < end_; b++) {    {
        int K = ((b%NBLOCKSDIM_K) << BLOCKDIMBITS_K);
        int t=b/NBLOCKSDIM_K;
        int J = ((t%NBLOCKSDIM_J) << BLOCKDIMBITS_J);
        t=t/NBLOCKSDIM_J;
        int I = ((t%NBLOCKSDIM_I) << BLOCKDIMBITS_I);
        float *localVol=Vol+b*BLOCKDIM_I*BLOCKDIM_J*BLOCKDIM_K;

        for(int i=I; i<I+BLOCKDIM_I; i++)
        {
          for(int j=J; j<J+BLOCKDIM_J; j++)
          {
            float tmp0 = c00*i+c01*j+c03;
            float tmp1 = c10*i+c11*j+c13;
            float tmp3 = c30*i+c31*j+c33;

            #pragma simd
            for(int k=K; k<K+BLOCKDIM_K; k++)
            {
                 float w=1/(tmp3+c32*k);
                 float lreal=w*(tmp1+c12*k); // y
                 float mreal=w*(tmp0+c02*k); // x
                 int l=(int)(lreal); float wl=lreal-l;
                 int m=(int)(mreal); float wm=mreal-m;

                 (*localVol) +=w*w*((1-wl)*((1-wm)*local_Raw[l*WinY +m ]+wm *local_Raw[l*WinY +m+1])
                                        +wl *((1-wm)*local_Raw[l*WinY+WinY+m ]
                                        +wm *local_Raw[l*WinY+WinY+m+1]));
                 localVol++;
            }
          }
        }
    }
  }
  return NULL;
}

下一步

要在英特尔® 至强 融核协处理器上成功调试您的应用请务必通读此指南并点击文中的超链接查看相关内容。本指南提供了实现最佳应用性能所要执行的步骤。

返回“高效并行化”章节

 


Nähere Informationen zur Compiler-Optimierung finden Sie in unserem Optimierungshinweis.