测量 CUDA 内核时的时间不同

Timings differ while measuring a CUDA kernel

我尝试测量 CUDA 内核函数所用的时间。我测量了它的 CPU 和 GPU 时序。但我发现两者之间存在巨大差异。

当我使用 NVIDIA 分析器分析它时,内核需要大约 6 毫秒,这正是我想要的。但是当我在内核调用周围使用 gettimeofday() 来获取 CPU 计时时,测量值为 15 毫秒。我在那里也没有任何 memcpy 调用。内核在单独的流中运行。并且类似的内核在并发流中运行。

示例代码:

gettimeofday(start);
cudaEventRecord(startGPU);

Kernel <<<abc, xyz,stream>>>();
cudaDeviceSynchronize();

cudaEventRecord(stopGPU);
printf("Elapsed GPU time  = ");

gettimeofday(stop);
printf("Elapsed CPU time  = ");

以上代码得到的结果:

经过的 GPU 时间 = 6 毫秒 经过 CPU 时间 = 15ms

这很奇怪,因为只存在内核执行行。然而,内核参数是指针。 mem 副本占用了额外的时间吗?但我也没有在配置文件的任何地方找到内存副本。任何线索将不胜感激。

基本上,您测量的 CPU 时间是

所需的时间
  1. 记录第一个事件,
  2. 使用相应的参数设置内核启动,
  3. 向 GPU 发送必要的命令,
  4. 在 GPU 上启动内核,
  5. 在GPU上执行内核,
  6. 等待 GPU 执行完成的通知返回 CPU,并且
  7. 记录第二个事件。

另请注意,您测量 CPU 时间的方法不仅仅测量您 process/thread 所花费的处理时间,而是测量系统耗用的总时间(可能包括处理时间被其他 processes/threads 花费,而你的 process/thread 甚至不一定 运行ning)。我必须承认,即使考虑到所有这些,与 GPU 时间相比,您报告的 CPU 时间仍然比我通常预期的要长得多。但我不确定上面是否真的是您的全部代码。事实上,我相当怀疑它,因为 printf() 并没有真正打印任何东西。因此,可能还有一些我们不知道的其他因素仍然需要考虑才能完全解释您的时间安排。

无论如何,您进行的两次测量很可能都没有真正测量您真正想要测量的东西。如果您对内核 运行 所需的时间感兴趣,请使用 CUDA 事件。然而,如果你先同步然后才记录结束事件,开始和结束事件之间的时间将是内核执行开始之间的时间,CPU 等待内核执行完成,以及它的任何时间可能需要记录第二个事件,然后让那个事件到达 GPU,这样您就可以询问 GPU 是什么时候收到它的。将事件想象成标记发送到 GPU 的命令流中的特定点的标记。很可能,你真的想写这个:

cudaEventRecord(startGPU, stream);       // mark start of kernel execution
Kernel<<<abc, xyz, stream>>>();
cudaEventRecord(stopGPU, stream);        // mark end of kernel execution
cudaEventSynchronize(stopGPU);   // wait for results to be available

然后使用cudaEventElapsedTime()得到两个事件之间的时间。

此外,请注意 gettimeofday()not necessarily a reliable way of obtaining high-resolution timings. In C++, you could use, e.g., std::steady_clock, or std::high_resolution_clock(只有在无法避免的情况下,我才会求助于后者,因为它不能保证稳定;并确保时钟周期实际上足以满足您要测量的内容)。

在调试相同的问题后,我发现 cuda 在第一次内核启动之前通常需要一些时间,如论坛中所述:https://devtalk.nvidia.com/default/topic/1042733/extremely-slow-cuda-api-calls-/?offset=3

内核之前的 cuda 运行时 API 有 6ms 的 cudaMalloc 和 14ms 的 cudaLaunch,这是额外延迟的原因。然而,随后的内核可以正常工作。 cudaLaunch 花费的时间通常以微秒为单位,因此如果超出这个范围,它肯定需要一些修复。

注意: 如果您是 运行 while(1) 循环中的任何 cuda 内核(仅一次),分配必须在循环外完成。否则你最终会像这样延迟。