在迭代循环中避免使用CudaMemcpy

时间:2015-05-08 08:24:08

标签: while-loop cuda

我想知道在Cuda中执行以下操作的最佳方法是什么:想象*你有一个长数组,并希望所有元素的总和低于1.如果总和大于1,则将每个元素除以2并再次计算总和。除以2并计算总和是在gpu上完成的。我现在的问题是:在cpu方面检查总和是否低于1的最佳方法是什么?我可以在每次迭代中做cudaMemcpy,但我也读过(并且已经看到)最好尽可能少地在两个内存之间进行传输。我找到了动态并行并认为我可能启动一个内核,一个块和一个线程执行while循环并调用sum并划分内核,但遗憾的是我的硬件只具有计算能力3.2和动态并行性仅以3.5开头。那么除了每次迭代执行cudaMemcpy以告诉cpu它可以停止执行while循环之外还有其他方法吗?

*上述算法只是解释情况的玩具问题(希望如此)。实际的算法是newton-raphson方法,但我的问题对任何迭代方法仍然有效,我必须决定是否停止或不给出在gpu上计算的值。

1 个答案:

答案 0 :(得分:3)

对于计算能力> = 3.5,正确识别的答案可能是动态并行性。

对于计算能力< 3.5事情不太清楚。有两种选择:第一种是查看memcpy和内核启动的延迟成本。第二种是使用更先进的技术来更好地控制你的块。

优化延迟

如果使用memcpy,请确保在启动memcpy之前不要同步。如果您不进行同步,则内核可能会隐藏与该副本相关的大部分开销。

尽管如此,这种情况的最低延迟路径可能是使用映射内存找到的:http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#mapped-memory。通过使用映射内存,内核将直接写入主机内存,而无需显式启动cudaMemcpy。

区块控制

对于这个问题,我们实际上并不需要全局同步,所以通过聪明,我们可以避免一些去主机的旅行。在这种情况下,我会考虑过度订阅GPU。如果您知道需要x个块来完成问题的迭代,请考虑启动,例如,5x块。因为未定义块的启动顺序,所以需要使用原子创建一个排序(每个块以原子方式递增一次全局整数)。

通过此块排序,您现在可以知道哪些块将参与迭代的第一步。任何没有参与第一次迭代的块都可以通过旋转标志来等待:

do {
  locked = volatileLoad(flag); // Make sure this is volatile
}
while (locked);

一旦第一批块完成其操作,并且输出被写入全局内存,您可以设置标志(确保正确使用threadfence!),允许下一步的块启动。然后,这些块可以执行下一步,也可以立即返回(如果您的条件已满足,则在允许块继续之后继续)。

这样做的最终结果是我们已经在GPU上准备好了等待启动的块。通过管理我们的块排序,我们知道每次迭代总是有足够的块来完成,因此旋转块将始终被释放。您需要确保的三件事是正确的:

  1. 您可以使用atomics管理自己的块ID。
  2. 使用volatile关键字加载标志以确保读取正确的值。
  3. 在允许依赖块继续之前,应用threadfence以确保输出可见。
  4. 显然不太可能启动正确数量的块,因此您必须不时返回主机以启动更多块。启动太多块的开销不会太糟糕,但也会带来风险。

    在实施此功能之前,请确保副本的延迟成本实际上导致显着减速。复制到主机并有条件地启动另一个内核的开销应该是每次迭代20微秒的量级。这种方法会给你的代码增加很多复杂性,所以你必须确保你需要保存这些微秒!

相关问题