【问题标题】:Parallelize do...while using OpenCL在使用 OpenCL 时并行化...
【发布时间】:2012-08-29 13:49:20
【问题描述】:

原理

我知道,这么简单的计算不值得精心并行化。就是这样一个例子,数学运算只是一些更有趣的计算的占位符。

[伪代码]

var id = 0,
do {
    id = getGlobalId();
    output[id] = input[id] * input[id];
} while (inRange(id) && output[id] !== 25);

最特殊的表达式可能是:output[id] !== 25。这意味着:如果input 有四个元素(按这个 顺序):[8, 5, 2, 9],那么output 应该是[64, 25]29 的平方不会用作output 的项目(因为对于id = 1input[id] = 5output[id] !== 25true)。

如果您正在优化这段代码,您可能需要提前计算每个input[id] 的平方(不证明第二个while 条件),但不能保证结果在以后是相关的(如果先前计算的结果是 25,则当前计算的结果是无意义的)。

概括地说,我说的是计算 result output[id] (output[id] = calculateFrom(input[id]);) 可能与每个 id 都不相关的情况 - 需要结果 (output[id]) 取决于另一个计算的结果。

我的目标

我想使用 OpenCL 内核和队列以尽可能并行和高性能的方式执行此循环。

我的想法

  • 我想:为了能够并行化这样的do...while循环 我们应该提前同时做一些计算(output[id] = calculateFrom(input[id]);)(没有 知道结果output[id] 是否有用)。如果结果 前一个是25,然后结果output[id] 简单地得到 被拒绝。

  • 也许我们应该考虑output[id] !== 25的概率。 如果概率非常高,我们不会提前做很多计算 时间,因为他们的结果可能会被拒绝。如果 概率绝对低,那我应该做更多的计算 提前。

  • 我们应该监听处理单元的当前状态。如果 已经超负荷了,我们不应该提前做无关紧要的事情 计算。但是如果有足够的资源来处理 提前计算,为什么不呢。 - 因为:如果提前计算和之前的计算(这些提前计算所依赖的)被同时处理,那么提前附加也可能减慢之前的计算 - (见我的第二个问题)

我的问题

  1. 并行化此类程序是明智的还是高性能的?
  2. 我应该根据哪些标准来决定处理单元是否有足够的资源来执行我的提前计算任务?或者:我如何知道我的处理单元是否过度紧张
  3. 您知道任何其他并行化此类do...whiles 的计划吗?您对此有什么想法吗?

我希望我想告诉你的总是很清楚。但如果不是,请评论我的问题。 - 感谢您的回答和帮助。

【问题讨论】:

  • 没有现成的答案,你应该自己看看你的确切情况。如果每个 id 拥有 output[id] == 25 的概率为 10%,那么平均而言,您必须处理 9 个元素,而且很可能不值得并行化。如果概率为 0.001%,则平均需要处理 99 999 个元素,如果数组有 10 000 个元素,则可以先计算所有元素,然后将垃圾丢弃;但是如果您的数组有 1 000 000 个元素,您可以分块处理它,并行处理每 100 000 个元素并使用一些原子标志来监视25
  • @aland 感谢您的图形描述。 - 但是,正如我的问题文本中所述,我不想让“并行化程度”(分别是工作组的规模)仅取决于条件适用的概率。我也想让它取决于处理单元的当前状态(是否过度紧张?)。 (在问题和 cmets 中对 mfa 的回答有更多的关注)
  • @aland 那么,您对算法有什么想法吗?它可以决定提前多少计算和我们应该并行化做,根据clGetDeviceInfo()提供的信息? - 感谢您对此的任何想法。
  • @frodijet:对于 OpenCL,您通常无法动态控制设备负载。你准备好数据,在这个数据上启动内核并等待它完成。工作组的最佳大小取决于设备和算法。如果您使用 nVidia GPU 计算设备占用率,则可以使用 CUDA 占用率计算器,或者手动进行(在大多数 OpenCL 优化指南中进行了描述)。对于 GPU,您应该针对数万个线程,并记住,在某些系统上,内核执行有大约 30 秒的超时时间,这可能会给您一些估计。

标签: parallel-processing cpu opencl gpu aparapi


【解决方案1】:

如果您使用单个工作组并利用设备的本地内存,则可以轻松地并行完成这项工作。 opencl 规范说至少有 16kb 的内存可用于此目的。

一些伪ocl代码:

__kernel doWhileTest(...){

  local int outputBuff[groupSize];
  local int loopBreak[groupsize];

  loopBreak[localId] = 0;
  barrier();

  for(int i = localId;loopBreak[0]==0;i+=groupSize){
    if(i<maxIndex){
      //do interesting calculation on globalInputValues[i]
      //save result to outputBuff[localId]
      //condition failed? set loopBreak[localId] to 1
    }else{
      //set loopBreak condition here as well
    }

    barrier();
    if(localId ==0){
      //loop through all loopBreak values (for j = 0..groupSize)
      //0? copy outputBuff[j] to global memory (globalInputValues[i+j])
      //1? set loopBreak[0] to 1 as well and break this inner loop
    }
    barrier();
  }

  //optional: write some default value to the remaining global buffer, or save a count of the valid outputs

}

上面的 for 循环可能看起来有点奇怪,因为索引是在循环内与 maxIndex 进行比较,而不是在 for 语句中。这是因为所有工作项都需要到达循环内的两个障碍,如果某些工作项提前爆发(即 maxIndex 不是 groupSize 的倍数),这是不可能的。 Related question re: barriers

此外,我在这里避免全局原子写入,因为它们的性能通常不如本地共享数据/暂存器内存,特别是因为您一次读取/写入整个数据范围。

使用上述设计,可以预先计算一些值,而不会过度处理。在使循环并行的同时,您最多只能丢弃 groupSize 结果。您也可以同时启动许多这样的工作组,但它们会根据自己的数据(而不是全局值或中断条件)各自爆发(或不爆发)。在这种情况下,也许将 groupSize 保持在较低的水平,这样不会破坏的组就不会花费太多时间来处理。 (也不要尝试使用全局中断值。我已经自旋锁定了我的 gpu 尝试这个,并得到一个白屏死机。)

【讨论】:

  • 感谢您的代码。 - 但我也谈到了如何找出哪个groupSize 是理想的。如果'groupSize'很小,那么并行性太小,如果groupSize很大,那么我做了很多可能会被拒绝的操作。 --- 扔掉一些不需要的操作结果不是一个真正的问题。但是当处理单元已经过度紧张时,我担心通过执行太多(可能不需要的)操作来减慢处理速度。
  • 那么你有没有一个想法如何通过分析clGetDeviceInfo()提供的信息来动态选择理想的groupSize? - 感谢您提供有关此的任何提示。
  • 我认为这取决于您的团队达到提前休息条件的几率。所有工作项将至少循环一次,如果没有人跳出循环,它们将再次循环。我上面的粗略示例需要每个工作项 2 个整数 LDS,因此这是一个可能的最大值。 (可以减少到 1 个 int 和 1 个 char。)我在这里对工作组大小问题有一个一般性的答案:stackoverflow.com/questions/10096443/… 你需要像往常一样对结果进行基准测试。
猜你喜欢
  • 2019-01-12
  • 1970-01-01
  • 1970-01-01
  • 2020-04-08
  • 2013-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-17
相关资源
最近更新 更多