OpenCL - 计算期间的增量求和

时间:2012-10-05 05:40:15

标签: opencl

我绝对是OpenCL编程的新手。对于我的应用程序(分子模拟)我写了一个用于计算lennard-jones液体分子间势的核。在这个内核中,我需要用一个计算所有粒子的潜在累积值:

__kernel void Molsim(__global const float* inmatrix, __global float* fi, const int c, const float r1, const float r2, const float r3, const float rc, const float epsilon, const float sigma, const float h1, const float h23)
{
   float fi0;
   float fi1;
   float d;

   unsigned int i = get_global_id(0); //number of particles (typically 2000)

   if(c!=i) {
      // potential before particle movement
      d=sqrt(pow((0.5*h1-fabs(0.5*h1-fabs(inmatrix[c*3]-inmatrix[i*3]))),2.0)+pow((0.5*h23-fabs(0.5*h23-fabs(inmatrix[c*3+1]-inmatrix[i*3+1]))),2.0)+pow((0.5*h23-fabs(0.5*h23-fabs(inmatrix[c*3+2]-inmatrix[i*3+2]))),2.0));
      if(d<rc) {
        fi0=4.0*epsilon*(pow(sigma/d,12.0)-pow(sigma/d,6.0));
      }
      else {
        fi0=0;
      }
      // potential after particle movement
      d=sqrt(pow((0.5*h1-fabs(0.5*h1-fabs(r1-inmatrix[i*3]))),2.0)+pow((0.5*h23-fabs(0.5*h23-fabs(r2-inmatrix[i*3+1]))),2.0)+pow((0.5*h23-fabs(0.5*h23-fabs(r3-inmatrix[i*3+2]))),2.0));
      if(d<rc) {
        fi1=4.0*epsilon*(pow(sigma/d,12.0)-pow(sigma/d,6.0));
      }
        else {
          fi1=0;
        }
      // cumulative difference of potentials
      // fi[0]+=fi1-fi0; changed to full size vector
      fi[get_global_id(0)]=fi1-fi0;
      }
}         

我的问题在于:fi [0] + = fi1-fi0 ;.在单元素向量中,fi [0]是错误的结果。 我读了一些关于总和减少的东西,但我不知道在计算过程中该怎么做。

存在我的问题的任何简单解决方案?

通知: 我尝试为向量组件的总和添加下一个内核(参见下面的代码),但是比使用CPU对向量求和时有更大的减速。

__kernel void Arrsum(__global const float* inmatrix, __global float* outsum, const int inmatrixsize, __local float* resultScratch)
{
       // načtení indexu
      int gid = get_global_id(0);
      int wid = get_local_id(0);
      int wsize = get_local_size(0);
      int grid = get_group_id(0);
      int grcount = get_num_groups(0);

      int i;
      int workAmount = inmatrixsize/grcount;
      int startOffest = workAmount * grid + wid;
      int maxOffest = workAmount * (grid + 1);
      if(maxOffest > inmatrixsize){
        maxOffest = inmatrixsize;
    }

    resultScratch[wid] = 0.0;
    for(i=startOffest;i<maxOffest;i+=wsize){
            resultScratch[wid] += inmatrix[i];
    }
    barrier(CLK_LOCAL_MEM_FENCE);

    if(gid == 0){
            for(i=1;i<wsize;i++){
                    resultScratch[0] += resultScratch[i];
            }
            outsum[grid] = resultScratch[0];
    }
}

3 个答案:

答案 0 :(得分:2)

我认为你需要fi [0] + = fi1-fi0的atomic_add原子函数;

警告:使用原子功能会降低性能。

这里有两个带增量原子函数的例子。

没有原子功能和2个工作项的示例:

__kernel void inc(global int * num){
    num[0]++; //num[0] = 0
}
  1. 工作项1读取num [0]:0
  2. 工作项2读取num [0]:0
  3. 工作项1递增num [0]:0 + 1
  4. 工作项2递增num [0]:0 + 1
  5. 工作项1写入num [0]:num [0] = 1
  6. 工作项2写入num [0]:num [0] = 1
  7. 结果:num [0] = 1

    原子功能和2个工作项的示例:

    #pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable
    
    __kernel void inc(global int * num){
        atom_inc(&num[0]);
    }
    
    1. 工作项1读取num [0]:0
    2. 工作项1递增num [0]:0 + 1
    3. 工作项1写入num [0]:num [0] = 1
    4. 工作项2读取num [0]:1
    5. 工作项2递增num [0]:1 + 1
    6. 工作项2写入num [0]:num [0] = 2
    7. 结果:num [0] = 2

答案 1 :(得分:0)

原子添加是一种解决方案,但您可能会遇到性能问题,因为原子部件会序列化您的工作项。

我认为,对于每个工作项,更好的解决方案是在自己的变量中编写,例如:

  

fi [get_global_id(0)] + = fi1-fi0;

然后你可以将数组传输到CPU并对所有元素求和,或者你在GPU上用算法并行执行它。

答案 2 :(得分:0)

所有线程都由“组”执行。您可以使用get_local_id(dim)函数确定组中的线程ID。每个组中的线程可以使用共享内存(在OpenCL中称为“本地内存”)并同步它们的执行,但不同组中的线程无法直接通信。

因此,减少的典型解决方案如下:

  1. 将临时数组part_sum(global)和tmp_reduce(local)添加到内核args:

    __kernel void Molsim(..., __global float *part_sum, __local float *tmp_reduce)
    
  2. 分配大小等于内核组数(= global_size / local_size)的浮点数组,并设置参数part_sum。

  3. 使用内核“local size”x size_of(float)和NULL设置参数tmp_reduce:

    clSetKernelArg(kernel,<par number>,sizeof(float)*<local_size>,NULL);
    
  4. 在内核中,将代码替换为:

      int loc_id=get_local_id(0);
    
    ...
    
    //      fi[0]+=fi1-fi0;
           tmp_reduce[loc_id]=fi1-fi0;
          }
       barrier(CLK_LOCAL_MEM_FENCE);
       if(loc_id==0) {
         int i;
         float s=tmp_reduce[0];
         for(i=1;i<get_local_size(0);i++)
           s+=tmp_reduce[i];
         part_sum[get_group_id(0)]=s;
       }
    }
    
  5. 完成内核执行后,只需在主机上总结part_sum [array]的内容,它比global_size小得多。

  6. 这不是完全“并行缩减”,因为您可以使用更复杂的算法使用Log2(local_size)操作并行地对tmp_reduce数组求和,但这必须比原子操作快得多。

    另外,请查看http://developer.amd.com/Resources/documentation/articles/pages/OpenCL-Optimization-Case-Study-Simple-Reductions_2.aspx的beter parallel reduction方法。