在迭代循环中避免 CudaMemcpy
Avoiding CudaMemcpy in an iterative loop
我想知道在 Cuda 中执行以下操作的最佳方法是什么:假设*您有一个长数组并且希望所有元素的总和小于 1。如果总和大于 1,则除以每个元素乘以 2 并再次计算总和。除以二并计算总和是在 gpu 上完成的。我现在的问题是:在 cpu 端检查总和是否小于 1 的最佳方法是什么?我可以在每次迭代中执行 cudaMemcpy,但我也读到(并且已经看到)最好在两个内存之间进行尽可能少的传输。我发现 动态并行性 并认为我可能会启动一个内核,其中包含一个块和一个执行 while 循环并调用求和和除法内核的线程,但不幸的是我的硬件只有 3.2 的计算能力而动态并行性仅从 3.5 开始。那么,除了每次迭代都执行 cudaMemcpy 来告诉 cpu 它可以停止执行 while 循环之外,还有其他方法吗?
*上面的算法只是一个用来解释情况的玩具问题(希望如此)。实际算法是 newton-raphson 方法,但我的问题对于任何迭代方法仍然有效,我必须决定是否停止或不给出在 gpu 上计算的值。
对于 >= 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!)允许下一步的块开始。然后这些块可以执行下一步,或者如果您的条件已经满足,则立即 return (在允许依赖于它们的块继续之后)。
这样做的最终结果是我们已经在 GPU 上准备好块等待启动。通过管理我们的块排序,我们知道每次迭代总是会有足够的块来完成,所以旋转块总是会被释放。您需要确保正确的三件事是:
- 您使用原子管理您自己的块 ID。
- 您使用 volatile 关键字加载标志以确保读取正确的值。
- 在允许相关块继续之前,您应用了一个 threadfence 以确保输出可见。
显然不太可能启动正确数量的块,因此您将不得不不时返回主机以启动更多块。启动太多块的开销应该不会太糟糕,但也会有风险。
在执行此操作之前,请确保您的副本的延迟成本确实导致了显着的速度下降。复制到主机并有条件地启动另一个内核的开销应该是每次迭代 20 微秒的量级。这种方法会给您的代码增加相当多的复杂性,因此请确保您需要节省这些微秒!
我想知道在 Cuda 中执行以下操作的最佳方法是什么:假设*您有一个长数组并且希望所有元素的总和小于 1。如果总和大于 1,则除以每个元素乘以 2 并再次计算总和。除以二并计算总和是在 gpu 上完成的。我现在的问题是:在 cpu 端检查总和是否小于 1 的最佳方法是什么?我可以在每次迭代中执行 cudaMemcpy,但我也读到(并且已经看到)最好在两个内存之间进行尽可能少的传输。我发现 动态并行性 并认为我可能会启动一个内核,其中包含一个块和一个执行 while 循环并调用求和和除法内核的线程,但不幸的是我的硬件只有 3.2 的计算能力而动态并行性仅从 3.5 开始。那么,除了每次迭代都执行 cudaMemcpy 来告诉 cpu 它可以停止执行 while 循环之外,还有其他方法吗?
*上面的算法只是一个用来解释情况的玩具问题(希望如此)。实际算法是 newton-raphson 方法,但我的问题对于任何迭代方法仍然有效,我必须决定是否停止或不给出在 gpu 上计算的值。
对于 >= 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!)允许下一步的块开始。然后这些块可以执行下一步,或者如果您的条件已经满足,则立即 return (在允许依赖于它们的块继续之后)。
这样做的最终结果是我们已经在 GPU 上准备好块等待启动。通过管理我们的块排序,我们知道每次迭代总是会有足够的块来完成,所以旋转块总是会被释放。您需要确保正确的三件事是:
- 您使用原子管理您自己的块 ID。
- 您使用 volatile 关键字加载标志以确保读取正确的值。
- 在允许相关块继续之前,您应用了一个 threadfence 以确保输出可见。
显然不太可能启动正确数量的块,因此您将不得不不时返回主机以启动更多块。启动太多块的开销应该不会太糟糕,但也会有风险。
在执行此操作之前,请确保您的副本的延迟成本确实导致了显着的速度下降。复制到主机并有条件地启动另一个内核的开销应该是每次迭代 20 微秒的量级。这种方法会给您的代码增加相当多的复杂性,因此请确保您需要节省这些微秒!