在OpenCL 1.2上实现堆栈推送的正确方法是什么?

时间:2018-05-23 22:38:56

标签: algorithm data-structures stack opencl

问题

假设多个工作项想要附加到全局堆栈:

void kernel(__global int* stack) {
    ... do stuff ...
    push(stack, value);
    ... do stuff ...
    return y;
}

在内核运行之后,stack包含推送到它的每个value是可取的。订单无关紧要。在OpenCL 1.2中执行此操作的正确方法是什么?

我尝试了什么

一个明显的想法是使用atomic_inc来获取长度并写入:

void push(__global int* stack, int val) {
    int idx = atomic_inc(stack) + 1; // first element is the stack length
    stack[idx] = val;
}

但我推测所有工作项都会在同一个内存位置分别调用atomic_inc,这会破坏并行性。另一个想法是只写一个大于工作项数的临时数组:

void push(__global int* stack, int val) {
    stack[get_global_id(0)] = val;
}

这给我们留下了稀疏的数值:

[0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 3, 0, 0, 0, 9, 0, 0, ...]

然后可以使用"stream compaction"来压缩。因此,我想知道这些想法中哪些是最有效的,如果可能还有第三种选择我不知道。

1 个答案:

答案 0 :(得分:2)

我无法在这里给你一个明确的答案,但我可以提出一些建议 - 如果你有资源,尝试实施不止一个,并分析他们在所有不同的表现您计划部署的OpenCL实施类型。您可能会发现不同的解决方案在不同的硬件/软件上的表现不同。

  1. 在本地内存中为每个工作组创建一个堆栈(显式或在生成所有值后通过压缩)并且仅按工作组计数递增全局堆栈并将整个本地堆栈复制到全局堆栈中。这意味着每个工作组只有一个全局原子添加。当然,对于大型团队来说效果更好。
  2. 天真方法中最大的原子争用来源是来自同一工作组的项目。因此,您可以创建与每个工作组的项目一样多的堆栈,并让组中的每个项目都提交给其自己的"堆。在此之后,您仍然需要一个压缩步骤将它们全部合并到一个列表中。如果您尝试这个,可以改变组的大小。我不确定当前GPU在多大程度上遭受错误共享(原子锁定整个缓存行,而不仅仅是那个词)所以你要检查和/或试验堆栈计数器之间的不同差距存储器中。
  3. 将所有结果写入固定偏移量(基于全局id)一个足以捕获最坏情况的数组,并将一个单独的压缩内核排队,后者将结果后处理成一个连续的数组。
  4. 不要为结果的紧凑表示而烦恼。相反,使用稀疏数组作为下一个计算阶段的输入。下一阶段的工作组可以将稀疏阵列的固定子集压缩到本地存储器中。完成后,每个工作项然后处理压缩数组的一个项目。在内核中迭代,直到所有内容都被处理完毕。它的工作原理将取决于数组中稀疏项的统计分布的可预测性,以及您选择的工作组大小以及每个工作组处理的稀疏数组的大小。此版本还避免了往返主处理器的往返。
  5. 特别是在Intel IGP上,我听说具有可变数量输出的DirectX / OpenGL / Vulkan几何着色器表现非常出色。如果您可以使用几何着色器的格式编写算法,那么如果您要定位这些设备,则可能值得一试。对于nvidia / AMD,不要为此烦恼。
  6. 可能有其他选择,但那些应该给你一些想法。