我有一个程序,当在使用nvprof进行分析后,表示大约98%的执行时间用于cudaDeviceSynchronize。在考虑如何优化以下代码时,我回到这里试图确认我对cudaDeviceSynchronize需求的理解。
我的程序的总体布局是:
Copy input array to GPU.
program<<<1,1>>>(inputs)
Copy outputs back to host.
因此,我的程序内核是一个主线程,基本上是这样的:
for (int i = 0; i < 10000; i++)
{
calcKs(inputs);
takeStep(inputs);
}
calcKs函数是cudaDeviceSynchronize最令人震惊的滥用者之一,看起来像这样:
//Calculate k1's
//Calc fluxes for r = 1->(ml-1), then for r = 0, then calc K's
zeroTemps();
calcFlux<<< numBlocks, numThreads >>>(concs, temp2); //temp2 calculated from concs
cudaDeviceSynchronize();
calcMonomerFlux(temp2, temp1); //temp1 calculated from temp2
cudaDeviceSynchronize();
calcK<<< numBlocks, numThreads >>>(k1s, temp2); //k1s calculated from temp2
cudaDeviceSynchronize();
其中数组temp2,temp1和k1s各自根据彼此的结果计算。我的理解是cudaDeviceSynchronize是必不可少的,因为我需要在计算temp1之前完全计算temp2,对temp1和k1s也是如此。
我觉得我在阅读这篇文章时严重误解了cudaDeviceSynchronize的功能:When to call cudaDeviceSynchronize?。我不确定对我的情况有何评论是否相关,因为我的所有程序都在设备上运行,并且在最终内存复制回主机之前没有CPU-GPU交互,因此我没有得到由memCpy
引起的隐式序列化答案 0 :(得分:3)
发布到同一个流will be serialized的CUDA活动(内核调用,内存等)。
如果您在应用程序中根本不使用流,那么您所做的一切都在default stream。
因此,在您的情况下,:
之间没有功能差异calcFlux<<< numBlocks, numThreads >>>(concs, temp2); //temp2 calculated from concs
cudaDeviceSynchronize();
calcMonomerFlux(temp2, temp1); //temp1 calculated from temp2
和
calcFlux<<< numBlocks, numThreads >>>(concs, temp2); //temp2 calculated from concs
calcMonomerFlux(temp2, temp1); //temp1 calculated from temp2
您没有显示calcMonomerFlux
是什么,但假设它使用temp2
中的数据并且正在主机上进行计算,则必须使用cudaMemcpy
来获取temp2
数据实际使用之前的数据。由于cudaMemcpy
将被发送到与前一个内核调用相同的流(calcFlux
),它将被序列化,即它将在calcFlux
之前开始完成。根据{{1}}中的temp2
数据,您的其他代码可能会在calcMonomerFlux
之后执行,这是一个阻塞操作,因此在cudaMemcpy
完成之前它不会开始执行。< / p>
即使cudaMemcpy
包含对calcMonomerFlux
数据进行操作的内核,参数也是相同的。这些内核可能会发布到与temp2
相同的流(默认流),因此在calcFlux
完成之前不会开始。
因此几乎肯定不需要calcFlux
电话。
话虽如此,cudaDeviceSynchronize()
本身不应该消耗大量的开销。大多数执行时间归因于cudaDeviceSynchronize()
的原因是因为从主机线程的角度来看,这个序列是:
cudaDeviceSynchronize()
几乎所有时间都在calcFlux<<< numBlocks, numThreads >>>(concs, temp2); //temp2 calculated from concs
cudaDeviceSynchronize();
来电。内核调用是异步的,这意味着它启动内核然后立即将控制权返回给主机线程,从而允许主机线程继续。因此,内核调用的主机线程的开销可能低至几微秒。但是cudaDeviceSynchronize()
调用将阻塞主机线程,直到前面的内核调用完成。内核执行的时间越长,主机线程在cudaDeviceSynchronize()
调用时等待的时间就越长。因此,几乎所有主机线程执行时间似乎都花费在这些调用上。
对于正确编写的单线程,单(默认)流CUDA代码,主机线程中几乎不需要cudaDeviceSynchronize()
。在某些情况下,对于某些类型的调试/错误检查可能很有用,并且在您执行内核并希望在应用程序终止之前从内核中看到打印输出(cudaDeviceSynchronize()
)的情况下它可能很有用。