OpenCL-计算期间的增量求和

OpenCL-计算期间的增量求和,opencl,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 floa

我绝对是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;
      }
}         
\uuuuu内核无效Molsim(\uuuu全局常量浮点*inmatrix,\uuuu全局浮点*fi,常量int c,常量浮点r1,常量浮点r2,常量浮点r3,常量浮点rc,常量浮点ε,常量浮点sigma,常量浮点h1,常量浮点h23)
{
浮动fi0;
浮动fi1;
浮动d;
unsigned int i=get_global_id(0);//粒子数(通常为2000)
如果(c!=i){
//粒子运动前的势
d=sqrt(pow((0.5*h1晶圆厂(0.5*h1晶圆厂(inmatrix[c*3]-inmatrix[i*3])),2.0)+pow((0.5*h23晶圆厂(0.5*h23晶圆厂(inmatrix[c*3+1]-inmatrix[i*3+1])),2.0)+pow((0.5*h23晶圆厂(inmatrix[c*3+2]-inmatrix[i*3+2])),2.0);

如果(d我认为你需要为fi[0]+=fi1-fi0添加原子函数

警告:使用原子函数会降低性能

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

没有原子函数和两个工作项的示例:

__kernel void inc(global int * num){
    num[0]++; //num[0] = 0
}
#pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable

__kernel void inc(global int * num){
    atom_inc(&num[0]);
}
  • 工作项1读取num[0]:0
  • 工作项2读取num[0]:0
  • 工作项1增加num[0]:0+1
  • 工作项2递增num[0]:0+1
  • 工作项1写入num[0]:num[0]=1
  • 工作项2写入num[0]:num[0]=1
  • 结果:num[0]=1

    具有原子函数和2个工作项的示例:

    __kernel void inc(global int * num){
        num[0]++; //num[0] = 0
    }
    
    #pragma OPENCL EXTENSION cl_khr_global_int32_base_atomics : enable
    
    __kernel void inc(global int * num){
        atom_inc(&num[0]);
    }
    
  • 工作项1读取num[0]:0
  • 工作项1增加num[0]:0+1
  • 工作项1写入num[0]:num[0]=1
  • 工作项2读取num[0]:1
  • 工作项2增加数值[0]:1+1
  • 工作项2写入num[0]:num[0]=2

  • 结果:num[0]=2

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

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

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


    然后,您可以将数组传输到CPU并对所有元素求和,也可以在GPU上使用算法并行执行。所有线程都由“组”执行。您可以使用get_local_id(dim)函数确定组中的线程id。每个组中的线程都可以使用共享内存(称为“本地内存”)在OpenCL中)和同步它们的执行,但不同组中的线程不能直接通信

    因此,还原的典型解决方案如下:

      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;
       }
    }
    
  • 将临时数组part_sum(全局)和tmp_reduce(本地)添加到内核参数:

    __kernel void Molsim(..., __global float *part_sum, __local float *tmp_reduce)
    
  • 分配大小等于内核组数(=全局大小/局部大小)的浮点数组,并设置参数part\u sum

  • 将参数tmp_reduce设置为内核“local size”x size_的(float)和NULL:

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

      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;
       }
    }
    
    int loc\u id=get\u local\u id(0);
    ...
    //fi[0]+=fi1-fi0;
    tmp_reduce[loc_id]=fi1-fi0;
    }
    屏障(CLK_本地_MEM_围栏);
    如果(loc_id==0){
    int i;
    浮动s=tmp_减少[0];
    
    对于(i=1;我排除了iAtomic函数,因为fi必须是浮点变量是的,这是解决方案。但由于主机应用程序中的序列化求和,将降低性能。此外,将整个数组存储回CPU内存将比存储一个变量慢。在这一行中,它应该是:float s=tmp\u reduce[0]?但每次运行后,我都会得到不同的结果。这里可能需要求和归约,否则,对于GPU来说,原子或串行求和会很糟糕。这有点难掌握,但相对容易实现(特别是当要求和的元素数是二的幂时)考虑私有内存中的缓存值:<代码> in MatL[I* 3 + 0/1 / 2 ] < /代码>,因为您不止一次使用它。对于和,只使用一个约简算法。