OpenCL for循环执行模型

时间:2014-06-02 03:09:42

标签: loops parallel-processing opencl gpu

我目前正在学习OpenCL并遇到了这段代码:

int gti = get_global_id(0);
int ti = get_local_id(0);

int n = get_global_size(0);
int nt = get_local_size(0);
int nb = n/nt;

for(int jb=0; jb < nb; jb++) { /* Foreach block ... */
      pblock[ti] = pos_old[jb*nt+ti]; /* Cache ONE particle position */
      barrier(CLK_LOCAL_MEM_FENCE); /* Wait for others in the work-group */

      for(int j=0; j<nt; j++) { /* For ALL cached particle positions ... */
         float4 p2 = pblock[j]; /* Read a cached particle position */
         float4 d = p2 - p;
         float invr = rsqrt(d.x*d.x + d.y*d.y + d.z*d.z + eps);
         float f = p2.w*invr*invr*invr;
         a += f*d; /* Accumulate acceleration */
      }

      barrier(CLK_LOCAL_MEM_FENCE); /* Wait for others in work-group */
}

有关代码的背景信息:这是NBody仿真程序中OpenCL内核的一部分。可以找到整个代码和教程here

以下是我的问题(主要与for循环有关):

  1. OpenCL中执行for循环的确切方式是什么?我知道所有工作项都运行相同的代码,工作组中的工作项尝试并行执行。因此,如果我在OpenCL中运行for循环,这是否意味着所有工作项运行相同的循环,或者循环以某种方式划分为跨多个工作项运行,每个工作项执行循环的一部分(即工作项) 1处理索引0~9,项目2处理索引10~19等。

  2. 在这段代码片段中,外循环和内循环如何执行? OpenCL是否知道外部循环正在划分所有工作组之间的工作,并且内部循环试图将工作分成每个工作组中的工作项?

  3. 如果内部循环在工作项之间划分(意味着for循环中的代码并行执行,或者至少尝试执行),那么最后的添加如何工作?它本质上是做a = a + f * d,根据我对流水线处理器的理解,这必须按顺序执行。

  4. 我希望我的问题足够清楚,我感谢任何意见。

3 个答案:

答案 0 :(得分:3)

异构编程适用于工作分配模型,这意味着线程可以在其上工作并从中开始。

1.1)如您所知,线程在工作组(或线程块)中组织,在您的情况下,工作组(或线程块)中的每个线程将数据从全局内存传递到本地内存。

for(int jb=0; jb < nb; jb++) { /* Foreach block ... */
      pblock[ti] = pos_old[jb*nt+ti];

//I assume pblock is local memory

1.2)现在线程块中的所有线程都拥有本地存储所需的数据(因此不再需要进入全局内存)

1.3)现在进行处理,如果仔细查看处理发生的for循环

for(int j=0; j<nt; j++) {

运行的线程块总数。所以这个循环片段设计确保所有线程处理单独的数据元素。

1) for 循环就像OpenCL的另一个C语句一样,所有线程都会按原样执行它,它取决于你如何划分它。 OpenCL不会为你的循环内部做任何事情(比如#1.1点)。

2)OpenCL不了解您的代码,以及如何划分循环。

3)与语句相同:1内部循环不在线程之间划分,所有线程都将按原样执行,只有它们将指向他们想要处理的数据。

我想这对你来说很困惑是因为你在对线程块和本地内存有很多了解之前就跳进了代码。我建议你看看这段代码的初始版本,根本没有使用本地内存。

答案 1 :(得分:3)

  

在OpenCL中执行for循环究竟是什么?

  • 它们可以自动展开到代码页面中,使其更慢或更快完成。 SALU用于循环计数器,所以当你嵌套它们时,更多的SALU压力完成并且当嵌套超过9-10个循环时成为瓶颈(也许一些智能算法使用相同的计数器为所有循环应该做的伎俩)所以不做只有SALU在循环体中,但添加一些VALU指令,是一个加号。

  • 它们在SIMD中并行运行,因此除非有分支或内存操作,否则所有线程的循环都会相互锁定。如果一个循环正在添加某些东西,所有其他线程的循环也会添加,如果它们更快完成,它们会等待最后一个线程计算。当它们全部完成时,它们继续下一条指令(除非有分支或内存操作)。如果没有本地/全局内存操作,则不需要同步。这是SIMD,而不是MIMD,所以当循环在所有线程上没有做同样的事情时它是无效的。

  

在这段代码片段中,外循环和内循环如何执行?

  • nb和nt是常量,它们对于所有线程都是相同的,因此所有线程都执行相同的工作量。
  

如果内部循环在工作项之间划分

答案 2 :(得分:2)

  

1)在OpenCL中执行for循环的确切程度如何?我知道这一切   工作项运行相同的代码和工作组中的工作项   试图并行执行。所以,如果我在OpenCL中运行for循环,那么   这意味着所有工作项都运行相同的循环或以某种方式循环   分为运行多个工作项,每个工作项   执行循环的一部分(即工作项1处理索引0~9,   第2项处理指数10~19等。

你是对的。所有工作项都运行相同的代码,但请注意,它们可能无法以相同的速度运行相同的代码。只有逻辑上,它们运行相同的代码。在硬件中,同一波(AMD术语)或warp(NV术语)内的工作项,它们完全遵循指令级别的足迹。

就循环而言,它只不过是汇编代码级别中的一些分支操作。来自同一波的线程并行执行分支指令。如果所有工作项都满足相同条件,则它们仍然遵循相同的路径,并且并行运行。但是,如果他们不同意相同的条件,那么通常会有不同的执行。例如,在下面的代码中:

if(condition is true)
   do_a();
else
   do_b();

逻辑上,如果某些工作项满足条件,它们将执行do_a()函数;而其他工作项将执行do_b()函数。然而,实际上,波中的工作项在硬件中执行完全相同的步骤,因此,它们不可能并行地运行不同的代码。因此,一些工作项将被屏蔽掉do_a()操作,而wave执行do_a()函数;当它完成时,wave进入do_b()函数,此时,剩余的工作项被屏蔽掉。对于任一功能,只有部分工作项处于活动状态。

回到循环问题,因为循环是一个分支操作,如果某些工作项的循环条件为真,那么将发生上述情况,其中一些工作项在循环中执行代码,而其他工作项目将被掩盖。但是,在您的代码中:

for(int jb=0; jb < nb; jb++) { /* Foreach block ... */
      pblock[ti] = pos_old[jb*nt+ti]; /* Cache ONE particle position */
      barrier(CLK_LOCAL_MEM_FENCE); /* Wait for others in the work-group */

      for(int j=0; j<nt; j++) { /* For ALL cached particle positions ... */

循环条件不依赖于工作项ID,这意味着所有工作项将具有完全相同的循环条件,因此它们将遵循相同的执行路径并始终并行运行。

  

2)在这段代码片段中,外循环和内循环如何执行?   OpenCL是否知道外循环正在将所有工作分开   工作组和内循环试图划分工作   在每个工作组中的工作项目中?

如回答(1)所述,由于外圈和内圈的循环条件对于所有工件都是相同的,因此它们总是并行运行。

就OpenCL中的工作负载分配而言,它完全依赖开发人员来指定如何分配工作负载。 OpenCL对如何在工作组和工作项之间划分工作量一无所知。您可以通过使用全局工作ID或本地工作ID分配不同的数据和操作来对工作负载进行分区。例如,

unsigned int gid = get_global_id(0);
buf[gid] = input1[gid] + input2[gid];

此代码要求每个工作项从连续内存中获取两个数据,并将计算结果存储到连续的内存中。

  

3)如果内部循环在工作项之间划分(意味着   for循环中的代码并行执行,或至少执行   试图),最后的添加如何工作?它是   基本上做a = a + f * d,并从我对流水线的理解   处理器,这必须按顺序执行。

     float4 d = p2 - p;
     float invr = rsqrt(d.x*d.x + d.y*d.y + d.z*d.z + eps);
     float f = p2.w*invr*invr*invr;
     a += f*d; /* Accumulate acceleration */

这里,a,f和d在没有说明符的内核代码中定义,这意味着它们仅对工作项本身是私有的。在GPU中,这些变量将首先分配给寄存器;然而,寄存器通常是GPU上非常有限的资源,因此当寄存器用完时,这些变量将被放入专用存储器,这称为寄存器溢出(取决于硬件,它可能以不同的方式实现;例如,在一些平台,私有内存是使用全局内存实现的,因此任何寄存器溢出都会导致性能下降。

由于这些变量是私有的,因此所有工作项仍然并行运行,并且每个工作项都维护和更新它们自己的a,f和d,而不会相互干扰。