cudaElapsedTime与非默认流

时间:2018-03-15 10:40:35

标签: cuda gpu gpgpu

我的问题是使用函数 cudaEventElapsedTime 来衡量多流应用程序中的执行时间。 根据CUDA文件

  

如果最后一个事件记录在非NULL流中,结果时间可能大于预期(即使两者都使用相同的流句柄)。这是因为cudaEventRecord()操作异步发生并且无法保证测量的延迟实际上恰好在两个事件之间。 ny个其他不同的流操作可以在两个测量事件之间执行,从而改变时间显着。

我真的很难理解上面的粗体句子。看来,使用默认流测量时间更准确。但我想明白为什么?如果我想测量流中的执行时间,我发现通过该流而不是默认流附加启动/停止事件更合乎逻辑。有任何澄清吗?谢谢

1 个答案:

答案 0 :(得分:2)

首先让我们记住基本的CUDA stream语义:

  1. 发布到同一个流中的CUDA活动将始终按发布顺序执行。
  2. 在单独的流中发布的CUDA活动的执行顺序之间没有明确的关系。
  3. 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);
    

    我们应该期待什么时间? Kernel3start之间的经过时间会包含stop吗?

    事实上答案是未知的,并且可能因运行而异,并且可能取决于在上述活动之前和期间设备上发生的其他事情。

    对于上述问题顺序,并假设我们在设备上没有其他活动,我们可以假设在cudaEventRecord(start)操作之后,Kernel1将启动并开始执行。让我们假设它&#34;填充设备&#34;这样就不会有其他内核可以同时执行。我们还假设Kernel1的持续时间比Kernel2Kernel3的启动延迟时间长得多。因此,在执行Kernel1时,Kernel2Kernel3都排队等待执行。完成Kernel1后,设备计划程序可以选择 Kernel2Kernel3。如果它选择Kernel2,则在Kernel2完成后,它可以将stop事件标记为已完成,这将确定startstop之间的持续时间大约Kernel1Kernel2的持续时间。

    Device Execution: event(start)|Kernel1|Kernel2|event(stop)|Kernel3|
                                  |    Duration   |
    

    但是,如果调度程序选择在Kernel3之前开始Kernel2(基于流语义的完全合法且有效的选择),那么stop事件不能被标记为完成,直到{ {1}}完成,这表示测量的持续时间现在包括Kernel2Kernel1Kernel2的持续时间。 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()的定时,并恢复到高分辨率主机定时方法。无论如何,复杂的并发异步系统的时间安排是非常重要的。保守的建议旨在避免一些这些问题随意使用。