当要处理的元素数量随机增长时,保持固定的global_work_size和local_work_size是不是一个坏主意?

时间:2014-09-24 23:32:18

标签: opencl

通常建议保持global_work_size与您必须处理的“元素”的逻辑数量相同。不过,我的应用程序没有这样的东西。如果我有N个需要处理的元素,那么在单个内核传递之后,我会有M个元素 - 一个完全不同的数字,不依赖于N。< / p>

为了处理这种情况,我可以编写一个循环,例如:

while (elementsToBeProcessed)
    read "elementsToBeProcessed" variable from device
    enqueue ND range kernel with global_work_size = elemnetsToBeProcessed

但是每次传递需要一次读取。另一种方法是将所有内容保留在GPU中,只需调用enqueueNDRangeKernel 一次,使用固定的global_work_size和local_work_size匹配GPU布局,然后使用主线程同步其中的计算。

我的问题很简单:我的直觉是否正确,第二种选择更好,或者有没有理由与第一种选择一致?

5 个答案:

答案 0 :(得分:4)

这是一个棘手的问题,需要采取哪种方式。并且取决于您将要拥有的全球规模值以及它们随时间变化的程度。

每次传阅:(对于高度变化的值更好)

  • 适合全球规模,所有工作项目都会做有用的工作
  • 如果工作规模较小,则为HW提供不适合的本地大小
  • 阻止队列中的行为,设备利用率不佳
  • 易于理解和调试

修复了内核启动大小:(更适合稳定但值不断变化的值)

  • 未适合的全局大小,可能会浪费一些时间来运行null工作项
  • 将当地尺寸与设备相匹配
  • 非阻止行为,100%设备使用
  • 复杂调试

正如一些答案已经说过,OpenCL 2.0就是使用管道的解决方案。但是也可以使用另一个OpenCL 2.0特性,内核调用内核。这样你的内核就可以在没有CPU干预的情况下启动下一批内核。

答案 1 :(得分:1)

如果您可以避免在主机和设备之间传输数据总是好的,即使这意味着设备上的工作量也会增加。在许多应用中,数据传输是最慢的部分。

要找到更好的系统配置解决方案,您需要测试它们。如果您的目标是多个平台,那么第二个平台应该更快。但是有很多东西可以让它变慢。例如,它的代码可能更难为编译器优化,或者数据访问模式可能导致更多的缓存未命中。

如果您的目标是OpenCL 2.0,那么pipes可能是您想要查看此类随机数量元素的内容。 (在我因为不支持2.0的平台而得到一些投票之前,AMD已承诺今年推出2.0驱动程序)通过管道,你可以制作生产者内核和消费者内核。消费者内核可以在有足够的项目工作后立即开始工作。这可能会更好地利用所有资源。

答案 2 :(得分:1)

权衡:回读的性能是因为GPU将空闲等待工作,而如果你只是将一堆内核排入队列,那么它将保持忙碌。

简单:所以我认为答案取决于ToBeProcessed会有多少元素变化。如果一系列运行可能是(例如)20000,19760,15789,19345,那么我总是运行20000并且有一些空闲的工作项。另一方面,如果一个典型的模式是20000,4236,1234,9000,那么我会读回元素ToBeProcessed并将内核排列为只需要的内容。

高级:如果您的模式单调减少,您可以将回读与内核入队交错,这样您就可以始终保持GPU忙碌,但是您也可以随时将它们缩小。在每个内核入队之间启动一个elementToBeProcessed副本的异步双缓冲回读,并在接下来排队的那个之后将它用于内核

像这样:

  1. elementsToBeProcessedA =起始值
  2. elementsToBeProcessedB =起始值
  3. eventA = NULL
  4. eventB = NULL
  5. 使用NDRange of elementsToBeProcessedA
  6. 将内核排入队列
  7. 非阻塞clEnqueueReadBuffer for elementsToBeProcessedA,参加eventA
  8. 如果非null,请等待eventB,释放事件
  9. 使用NDRange of elementsToBeProcessedB
  10. 将内核排入队列
  11. 对于elementsToBeProcessedB的非阻塞clEnqueueReadBuffer,采用eventB
  12. 如果非null,请等待eventA,释放事件
  13. 转到5
  14. 这将使GPU完全饱和,但会使用更小的元素ToBeProcessed。它将处理elementsToBeProcessed 增加的情况,所以如果是这样的话,不要这样做。

答案 3 :(得分:1)

另一种解决方案:始终运行固定数量的全局工作项,足以填充GPU,但不能更多。然后,每个工作项应该查看要为此传递完成的项目总数(elementsToBeProcessed),然后执行总计的部分。

uint elementsToBeProcessed = <read from global memory>
uint step = get_global_size(0);

for (uint i = get_global_id(0); i < elementsToBeProcessed; i += step)
{
    <process item "i">
}

简化示例:全局工作大小为5(例如人为小),elementsToBeProcessed = 19:第一次通过循环元素0-4被处理,第二次通过5-9,第三次通过10-14,第四次通过15- 18。

您希望将固定的全局工作大小调整为与您的硬件完全匹配(计算单位*最大工作组大小或某些部分)。

这与工作项如何协作将数据复制到共享本地内存的算法没有什么不同。

答案 4 :(得分:0)

不必修复全局工作量。 E. g。你有128个流处理器。因此,您也可以创建一个本地大小为128的内核。您的全局工作大小可以是任何数字,该数字是该值的倍数 - 256,4096等。

虽然,本地组的大小通常由硬件规格决定。如果您需要处理更多数据,只需增加所涉及的本地组的数量。