在OpenCL中执行扫描

时间:2010-07-22 13:06:03

标签: opencl

我一直试图让一个简单的扫描工作很长一段时间。对于小问题,输出是正确的,但是对于大输出,我有时只得到正确的结果。我已经检查了Apple's OpenCL example,我基本上做了同样的事情(除了银行冲突,我忽略了atm)。所以这是第一阶段的代码:

__kernel void
scan_init(__global int * input,
          __global int * sums)
{
  int gid = get_global_id(0);
  int lid = get_local_id(0);
  int chunk_size = get_local_size(0)*2;

  int chunk = gid/chunk_size;
  int offset = chunk*chunk_size;

  reduction(input, offset);

  // store sums
  if(lid==0)
  {
    sums[chunk] = input[(chunk+1)*chunk_size-1];
  }

  downsweep(input, offset);
}

还原功能本身:

void reduction(__global int * input,
      int offset)
{
 int stride = 1;
 int grp_size = get_local_size(0);
 int lid = get_local_id(0);

 for(int d = grp_size; d > 0; d>>=1)
 {
   barrier(CLK_GLOBAL_MEM_FENCE);

   if(lid < d)
   {
     int ai = stride*(2*lid+1)-1+offset;
     int bi = stride*(2*lid+2)-1+offset;
     input[bi] += input[ai];
   }

   stride *= 2;
  }
}

在第二阶段,部分和用于构建每个元素的总和:

void downsweep(__global int * input,
        const unsigned int offset)
{
  int grp_size = get_local_size(0);
  int lid = get_local_id(0);
  int stride = grp_size*2;

  for(int d = 1; d <= grp_size; d *=2)
  {
    barrier(CLK_GLOBAL_MEM_FENCE);

    stride >>=1;

    if(lid+1 < d)
    {
      int src = 2*(lid + 1)*stride-1+offset;
      int dest = src + stride;
      input[dest]+=input[src];
    }
  }
}

输入填充的大小是本地工作大小的倍数。每个工作组可以扫描两倍大小的块。我在sums数组中保存每个块的总和,我用它来检查结果。以下是1的数组输入大小4000的输出:

Chunk size: 1024
Chunks: 4
Scan global size: 4096
Local work size: 512
Sum size: 4
0:1024  1:1120  2:2904  3:928 

但是,预期结果将是

0:1024  1:1024  2:1024  3:928 

如果我再次运行代码,我会得到:

0:1056  1:5376  2:1024  3:928 
0:1024  1:1088  2:1280  3:992 
0:5944  1:11156 2:3662  3:1900  
0:7872  1:1056  2:2111  3:1248  

对内核的调用如下:

clEnqueueNDRangeKernel(cl_ctx->queue, scan_init, 1, NULL, &scan_global_size, &local_work_size, 0, NULL, NULL);

其中全局大小为4096且本地大小为512.如果我将本地工作组大小限制为64,则输出如下所示:

0:128  1:128  2:128  3:288  4:128  5:128  6:192  7:192  
8:192  9:254  10:128  11:256  12:128  13:360  14:128  15:128  
16:128  17:128  18:128  19:288  20:128  21:128  22:128  23:128  
24:192  25:128  26:128  27:192  28:128  29:128  30:128  31:32 

如果我将输入大小更改为512以及任何块大小,那么一切都很有效!

最后,当使用输入大小513和组大小256(即,我有两个块,每个有512个元素,第二个只有第一个元素设置为1)时,第一阶段的结果是:

0:1  1:2  2:1  3:6  4:1  5:2  6:1  7:14  
8:1  9:2  10:1  11:6  12:1  13:2  14:1  15:28  
16:1  17:2  18:1  19:6  20:1  21:2  22:1  23:14  
24:1  25:2  26:1  27:6  28:1  29:2  30:1  31:56  
32:1  33:2  34:1  35:6  36:1  37:2  38:1  39:14  
40:1  41:2  42:1  43:6  44:1  45:2  46:1  47:28  
48:1  49:2  50:1  51:6  52:1  53:2  54:1  55:14  
56:1  57:2  58:1  59:6  60:1  61:2  62:1  63:148   

它应该在哪里:

0:1  1:2  2:1  3:4  4:1  5:2  6:1  7:8  
8:1  9:2  10:1  11:4  12:1  13:2  14:1  15:16  
16:1  17:2  18:1  19:4  20:1  21:2  22:1  23:8  
24:1  25:2  26:1  27:4  28:1  29:2  30:1  31:32  
32:1  33:2  34:1  35:4  36:1  37:2  38:1  39:8  
40:1  41:2  42:1  43:4  44:1  45:2  46:1  47:16  
48:1  49:2  50:1  51:4  52:1  53:2  54:1  55:8  
56:1  57:2  58:1  59:4  60:1  61:2  62:1  63:64 

我的猜测是,不同线程同时访问相同数据是一个问题,但是,情况并非如此,因为每个工作组都在处理不同的输入数据块。任何有关此事的帮助将不胜感激!!

1 个答案:

答案 0 :(得分:4)

我怀疑问题与barrier()不是工作组间同步有关。每个工作组都有自己的障碍,您无法保证工作组本身的顺序。当您将输入集大小更改为512时,您可能会使所有工作组在同一个多处理器上运行,因此偶然会同步。

你的chunk变量是get_group_id(0)/ 2,这意味着你有两个完整的工作组分配给同一个chunk。你可能想要反过来。如果它们恰好以锁步方式运行,它们将简单地覆盖彼此的工作,因为它们的加载存储依赖性将匹配。否则,它们可能会或可能不会干扰,总是在多次求和值的方向上。

这个问题的提示在你自己的问题中:“每个工作组都可以扫描两倍大小的大块。”这应该意味着数组大小的一半的总工作量足够。

downweep()中的循环也有一个奇怪的地方。第一次迭代什么都不做; lid + 1&gt; = 1,d开始为1.这可能是一个无关紧要的多余迭代,但在计划中它是一个关闭。