Parallel processing OpenCL中部分和的计算

Parallel processing OpenCL中部分和的计算,parallel-processing,opencl,gpu,sequences,Parallel Processing,Opencl,Gpu,Sequences,1D数据集被划分为多个段,每个工作项处理一个段。它从段中读取多个元素?元素的数量事先不知道,每个段的数量不同 例如: +----+----+----+----+----+----+----+----+----+ <-- segments A BCD E FG HIJK L M N <-- elements in this segment 因此,一段中元素的绝对输出位置取决于前一段中元素的数量E位于位置4,因为段包含1

1D数据集被划分为多个段,每个工作项处理一个段。它从段中读取多个元素?元素的数量事先不知道,每个段的数量不同

例如:

+----+----+----+----+----+----+----+----+----+     <-- segments
  A    BCD  E    FG  HIJK   L    M        N        <-- elements in this segment
因此,一段中元素的绝对输出位置取决于前一段中元素的数量<代码>E位于位置4,因为段包含1个元素(A),段2包含3个元素


OpenCL内核将每个段的元素数写入本地/共享内存缓冲区,工作方式如下(伪代码)

kernelvoidk(
恒定uchar*输入,
全局整数*输出,
局部整数*段元素计数
) {
int段=获取本地id(0);
int count=计数元素(&input[段*段大小]);
段元素计数[段]=计数;
屏障(CLK_本地_MEM_围栏);
ptrdiff_t位置=0;
对于(int-previous_段=0;previous_段<段;++previous_段)
位置+=段元素计数[上一段];
全局整数*输出值(ptr=&output[position];
读取元素(&输入[段*段大小],输出\u ptr);
}
因此,每个工作项都必须使用循环计算部分和,其中id较大的工作项进行更多的迭代

在OpenCL1.2中,有没有更有效的方法来实现这一点(每个工作项计算序列的部分和,直至其索引)?OpenCL 2似乎为此提供了
work\u group\u scan\u inclusive\u add

您可以在log2(N)次迭代中使用以下方法进行N个部分(前缀)和:

offsets[get_local_id(0)] = count;
barrier(CLK_LOCAL_MEM_FENCE);

for (ushort combine = 1; combine < total_num_segments; combine *= 2)
{
    if (get_local_id(0) & combine)
    {
        offsets[get_local_id(0)] +=
            offsets[(get_local_id(0) & ~(combine * 2u - 1u)) | (combine - 1u)];
    }
    barrier(CLK_LOCAL_MEM_FENCE);
}
连续迭代将产生:

a     b+a   c        d+c

这是我们想要的结果

所以在第一次迭代中,我们将段元素计数分成两组,并在其中求和。然后我们一次将2个组合并为4个元素,并将结果从第一个组传播到第二个组。我们将组再次增加到8个,以此类推

关键的观察结果是,该模式还与每个段索引的二进制表示形式相匹配:

0: 0b00  1: 0b01  2: 0b10  3: 0b11
索引0不执行求和。索引1和3在第一次迭代中执行求和(位0/LSB=1),而索引2和3在第二次迭代中执行求和(位1=1)。这就解释了这一点:

    if (get_local_id(0) & combine)
另一个真正需要解释的说法当然是

        offsets[get_local_id(0)] +=
            offsets[(get_local_id(0) & ~(combine * 2u - 1u)) | (combine - 1u)];
计算我们想要累积到工作项总和上的前一个前缀总和的索引有点棘手。子表达式
(combine*2u-1u)
在每次迭代(对于从1开始的n)时取值(2n-1):

通过按位屏蔽这些位后缀(即,
i&~x
)工作项索引,这将为您提供当前组中第一个项的索引

(combine-1u)
子表达式然后为您提供上半部分最后一项的当前组内的索引。将这两者放在一起,可以得到要累积到当前段中的项目的总体索引

结果有一点不好:它向左移动了一个:因此段1需要使用偏移量[0],以此类推,而段0的偏移量当然是0。您可以将偏移量数组超分配1,并对从索引1开始的子数组执行前缀和,然后将索引0初始化为0,或者使用条件值

您可能可以对上述代码进行分析驱动的微优化

a     b+a   c+(b+a)  (d+c)+(b+a)
0: 0b00  1: 0b01  2: 0b10  3: 0b11
    if (get_local_id(0) & combine)
        offsets[get_local_id(0)] +=
            offsets[(get_local_id(0) & ~(combine * 2u - 1u)) | (combine - 1u)];
1 = 0b001
3 = 0b011
7 = 0b111
…