oneAPI GPU 优化指南 - Kernel 启动

本章节翻译by chenshusmail@163.com 原文:Kernel Launch (intel.com)

在 SYCL 中,通过将 kernel 排队到针对特定设备的队列中来执行工作。这些 kernel 由主机端提交给设备端, 由设备端执行并将结果发送回来。由主机端发起的 kernel 提交和实际开始执行并不立即发生 - 它们是异步的, 因此我们必须跟踪与 kernel 相关的以下时间。

  • Kernel 提交的开始时间

    这是主机端开始提交 kernel 进程的时间。

  • Kernel 提交的结束时间

    这是主机端完成提交 kernel 的时间。主机端执行多个任务,如排队参数, 在 runtime 为 kernel 在设备上开始执行分配资源。

  • Kernel 的启动时间

    这是由主机端提交的 kernel 在设备端开始执行的时间。请注意,这并不完全相同于 kernel 提交结束时间。 在提交结束时间和 kernel 启动时间之间存在延迟,这取决于设备的可用性。 主机端可能会在实际启动执行之前将多个 kernel 排队等待执行。此外, 在实际 kernel 开始执行之前需要进行一些数据传输,这个时间通常不会与 kernel 的启动时间分开计算。

  • Kernel 的完成时间

    这是 kernel 在设备端完成执行的时间。当前一代设备端是非抢占式的,这意味着一旦 kernel 启动, 它必须完成其执行。

像 Intel® VTuneTM Profiler (vtune), clIntercept, 和 onetrace 等工具为应用程序中每个 kernel 的上述每个时间提供了可视化时间线。

下面这个简单的例子展示了测量 kernel 执行的时间。这将涉及主机端的 kernel 提交时间、 设备端的 kernel 执行时间以及任何数据传输时间(由于没有 buffer 或 memory,所以这种情况下通常为零)。

void emptyKernel1(sycl::queue &q) {
  Timer timer;
  for (int i = 0; i < iters; ++i)
    q.parallel_for(1, [=](auto) {
       /* NOP */
     }).wait();
  std::cout << "  emptyKernel1: Elapsed time: " << timer.Elapsed() / iters
            << " sec\n";
} // end emptyKernel1

相同的代码,如果在 parallel_for 的末尾没有 wait(),那么它将测量主机端将 kernel 提交给 runtime 所需的时间。

void emptyKernel2(sycl::queue &q) {
  Timer timer;
  for (int i = 0; i < iters; ++i)
    q.parallel_for(1, [=](auto) {
      /* NOP */
    });
  std::cout << "  emptyKernel2: Elapsed time: " << timer.Elapsed() / iters
            << " sec\n";

这些开销高度依赖于所使用的后端 runtime 和主机端的处理能力。

一种测量设备端实际 kernel 执行时间的方法是使用 SYCL 内置的分析 API。 下面的代码演示了如何使用 SYCL 分析 API 来分析 kernel 执行时间。它还显示了 kernel 的提交时间。 由于它依赖于 runtime 和设备驱动程序,因此无法以编程方式测量 kernel 启动时间。分析工具可以提供这些信息。

#include <CL/sycl.hpp>

class Timer {
public:
  Timer() : start_(std::chrono::steady_clock::now()) {}

  double Elapsed() {
    auto now = std::chrono::steady_clock::now();
    return std::chrono::duration_cast<Duration>(now - start_).count();
  }

private:
  using Duration = std::chrono::duration<double>;
  std::chrono::steady_clock::time_point start_;
};

int main() {
  Timer timer;
  sycl::queue q{sycl::property::queue::enable_profiling()};
  auto evt = q.parallel_for(1000, [=](auto) {
    /* kernel statements here */
  });
  double t1 = timer.Elapsed();
  evt.wait();
  double t2 = timer.Elapsed();
  auto startK =
      evt.get_profiling_info<sycl::info::event_profiling::command_start>();
  auto endK =
      evt.get_profiling_info<sycl::info::event_profiling::command_end>();
  std::cout << "Kernel submission time: " << t1 << "secs\n";
  std::cout << "Kernel submission + execution time: " << t2 << "secs\n";
  std::cout << "Kernel execution time: "
            << ((double)(endK - startK)) / 1000000.0 << "secs\n";

  return 0;
}

下图显示了上述示例执行的时间线。这张图片是通过运行 clIntercept 生成跟踪文件并使用 Chrome* tracing 来生成可视化时间线的。在这个时间线中有两条轴线, 一个用于主机端,另一个用于设备端。注意, 在设备端唯一的活动是执行提交的 kernel。 在主机端完成了大量工作来准备 kernel 的执行。 在这种情况下,由于 kernel 非常小,总执行时间主要由 kernel 的 JIT 编译所占据, 即下图中标记为 clBuildProgram 的块。

kernel 执行的时间线

下图是放大版本,显示了主机端提交 kernel 时调用的函数的详细信息。 这里的时间主要由 clEnqueueNDRangeKernel 占据。另外请注意, 在主机上完成 kernel 提交和设备上实际启动 kernel 之间存在延迟。这是因为在主机上完成 kernel 提交后, 设备端需要一些时间来准备执行 kernel,这取决于设备的可用性和 runtime 的调度策略。

主机端提交 kernel 时调用的函数

上一章                                         主目录    上级目录                                                               下一章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值