【问题标题】:Avoiding CudaMemcpy in an iterative loop在迭代循环中避免 CudaMemcpy
【发布时间】:2015-07-19 02:14:58
【问题描述】:

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

*上面的算法只是一个解释情况的玩具问题(希望如此)。实际的算法是牛顿拉夫森方法,但我的问题对于任何迭代方法仍然有效,我必须决定是否停止给定在 gpu 上计算的值。

【问题讨论】:

  • "我的硬件只有计算能力3.2"是什么意思?
  • 有点像版本号。我使用 Jetson Tk1 板。它具有计算能力 3.2。这意味着它具有 Kepler 架构 (3) 并且启用了某些功能 (.2) (see here) 但不是 dynamic parallism 需要 3.5。再次阅读句子后sry,是的,缺少一些东西。

标签: while-loop cuda


【解决方案1】:

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

对于计算能力

延迟优化

如果使用 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. 您可以使用原子管理自己的块 ID。
  2. 您使用 volatile 关键字加载标志以确保读取正确的值。
  3. 您应用 threadfence 以确保在允许相关块继续之前输出可见。

显然不可能启动正确数量的块,因此您必须不时返回主机启动更多块。启动过多块的开销应该不会太高,但也会有风险。

在您实施此操作之前,请确保您的副本的延迟成本实际上会导致显着减速。复制到主机和有条件地启动另一个内核的开销应该是每次迭代大约 20 微秒。这种方法会给您的代码增加相当多的复杂性,因此请确保您需要保存这些微秒!

【讨论】:

  • 感谢您的回答。我认为你是对的,它会增加很多复杂性。所以我尝试映射内存。但我喜欢块控制的想法。也许我可以在其他地方使用它。无论如何,再次:谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-18
  • 1970-01-01
  • 2017-06-04
  • 2019-05-26
  • 2018-12-06
  • 1970-01-01
  • 2017-10-28
相关资源
最近更新 更多