我的问题是使用函数 cudaEventElapsedTime 来衡量多流应用程序中的执行时间。 根据CUDA文件
如果最后一个事件记录在非NULL流中,结果时间可能大于预期(即使两者都使用相同的流句柄)。这是因为cudaEventRecord()操作异步发生并且无法保证测量的延迟实际上恰好在两个事件之间。 ny个其他不同的流操作可以在两个测量事件之间执行,从而改变时间显着。
我真的很难理解上面的粗体句子。看来,使用默认流测量时间更准确。但我想明白为什么?如果我想测量流中的执行时间,我发现通过该流而不是默认流附加启动/停止事件更合乎逻辑。有任何澄清吗?谢谢
答案 0 :(得分:2)
首先让我们记住基本的CUDA stream语义:
CUDA default stream(假设我们没有覆盖默认的传统行为)具有隐式同步的附加特性,这大致意味着CUDA操作发布到默认流中的内容将不会开始执行,直到所有先前发布的到该设备的所有CUDA活动完成。
因此,如果我们将2个CUDA事件(例如,开始和停止)发布到旧版默认流中,我们可以确信将在这两个问题点之间发布的任何和所有CUDA活动将定时(无论它们被发布到哪个流,或者它们是从哪个主机线程发出的)。我建议随意使用这是直观的,不太可能被误解。此外,它应该产生一致的计时行为,run-to-run(假设主机线程行为是相同的,即以某种方式同步)。
OTOH,我们说我们有一个多流应用程序。让我们假设我们将内核发布到2个或更多非默认流中:
Stream1: cudaEventRecord(start)|Kernel1|Kernel2|cudaEventRecord(stop)
Stream2: |Kernel3|
这些是从同一主机线程还是从单独的主机线程发出并不重要。例如,让我们说我们的单个主机线程活动看起来像这样(浓缩):
cudaEventRecord(start, Stream1);
Kernel1<<<..., Stream1>>>(...);
Kernel2<<<..., Stream1>>>(...);
Kernel3<<<..., Stream2>>>(...);
cudaEventRecord(stop, Stream1);
我们应该期待什么时间? Kernel3
和start
之间的经过时间会包含stop
吗?
事实上答案是未知的,并且可能因运行而异,并且可能取决于在上述活动之前和期间设备上发生的其他事情。
对于上述问题顺序,并假设我们在设备上没有其他活动,我们可以假设在cudaEventRecord(start)
操作之后,Kernel1
将启动并开始执行。让我们假设它&#34;填充设备&#34;这样就不会有其他内核可以同时执行。我们还假设Kernel1
的持续时间比Kernel2
和Kernel3
的启动延迟时间长得多。因此,在执行Kernel1
时,Kernel2
和Kernel3
都排队等待执行。完成Kernel1
后,设备计划程序可以选择 Kernel2
或Kernel3
。如果它选择Kernel2
,则在Kernel2
完成后,它可以将stop
事件标记为已完成,这将确定start
和stop
之间的持续时间大约Kernel1
和Kernel2
的持续时间。
Device Execution: event(start)|Kernel1|Kernel2|event(stop)|Kernel3|
| Duration |
但是,如果调度程序选择在Kernel3
之前开始Kernel2
(基于流语义的完全合法且有效的选择),那么stop
事件不能被标记为完成,直到{ {1}}完成,这表示测量的持续时间现在包括Kernel2
加Kernel1
加Kernel2
的持续时间。 CUDA编程模型中没有任何内容可以对此进行排序,这意味着测量的时序甚至可以交替运行:
Kernel3
此外,我们可以大大改变实际的问题顺序,将Device Execution: event(start)|Kernel1|Kernel3|Kernel2|event(stop)|
| Duration |
的问题/发布置于之前的第一个Kernel3
或之后最后cudaEventRecord
,上述参数/变量仍然存在。这就是cudaEventRecord
调用的异步特性的含义。它不会阻塞CPU线程,但是像内核启动一样,它是异步的。因此,以上所有活动都可以在任何实际开始在设备上执行之前发出。即使cudaEventRecord
在第一个Kernel3
之前开始执行,它也会占用设备一段时间,从而延迟执行cudaEventRecord
,从而将测量的持续时间增加一些。
如果Kernel1
即使在最后Kernel3
之后发布,因为所有这些问题操作都是异步的,cudaEventRecord
可能仍然排队等待{{1完成后,意味着设备调度程序仍然可以选择启动哪个,从而实现可能的可变时序。
当然还有其他类似的危害可以勾勒出来。这种多流方案变化的可能性导致了保守建议,以避免尝试使用发布到非遗留默认流中的事件来进行基于Kernel3
的计时。
当然,如果您使用visual profiler,那么两个事件之间的测量结果应该相对较少(尽管它可能仍然在运行中变化)。但是,如果您要使用可视化分析器,则可以直接从时间线视图中读取持续时间,而无需事件过去时间调用。
请注意,如果您覆盖默认的流遗留行为,则默认流大致相当于&#34;普通&#34;流(特别是对于单线程主机应用程序)。在这种情况下,我们不能依赖于默认流语义来对其进行排序。一种可能的选择可能是在Kernel1
次呼叫之前进行cudaEvent
次呼叫。我并不建议对每种可能的情况进行排序,但对于单设备单主机线程应用程序,它应该等同于发布到默认旧流中的cudaEventRecord()
时间。
使用分析器最好完成复杂的场景定时。许多人还完全放弃了基于cudaDeviceSynchronize()
的定时,并恢复到高分辨率主机定时方法。无论如何,复杂的并发异步系统的时间安排是非常重要的。保守的建议旨在避免一些这些问题随意使用。