Intel® FPGA SDK for OpenCL™ Pro Edition: 最佳实践实践指南

ID 683521
日期 9/26/2022
Public
文档目录

11.2. 使用硬件内核调用队列

OpenCL内核是通过调用队列构建的,因而可以立即启动下一个调用。
提示: 如果您需要特定于 Intel® oneAPI DPC++/C++ Compiler的详细信息,请参阅 Intel® oneAPI Toolkits的FPGA优化指南中的使用硬件内核调用列队主体。

如下图所示,使用调用队列时,系统和OpenCL运行时环境开销(从响应完成到发送下一组调用参数)与内核执行重叠。这样使得内核连续执行,并最大限度地提高系统吞吐量。

图 92. 有调用队列和无调用队列的内核执行

当另一个具有相同内核函数名称和程序的列队内核已经在器件上运行时,内核调用在硬件上列队起来,并且满足以下条件:

  • OpenCL内核编译未使用禁用硬件内核调用缓冲区(-no-hardware-kernel-invocation-buffer)。
  • OpenCL内核编译未使用性能计数器(-profile
  • 列队的OpenCL内核没有printf
  • 命令列队中较早列队的所有事件对象的执行状态都等于CL_COMPLETE

    如果状态为CL_SUBMITTED或者CL_RUNNING,则该状态适用于同一程序中具有相同内核函数名称的已列队内核。

  • 事件等待列表中所有事件对象的执行状态等于CL_COMPLETE

    如果状态为CL_SUBMITTED或者CL_RUNNING,则该状态适用于相同器件上同一程序中具有相同内核函数名称的已列队内核。

  • 如果OpenCL内核使用异构存储器,则当前在器件上运行的内核和正在列队的内核不会在不同存储器类型上设置相同的存储器对象。

请参考以下两个实例主机代码片段,其中编译器可以将内核调用列入硬件内核调用队列:

实例1

int main()
{	…
  clEnqueueNDRangeKernel(queue, kernel, …, NULL);
  clEnqueueNDRangeKernel(queue, kernel, …, NULL);
  …
}

一旦第一个入列的内核运行,编译器可以将第二个入列的内核列入硬件上。

实例 2

int main()
{	…
  clEnqueueNDRangeKernel(queue0, kernel0, …, NULL);
  clEnqueueNDRangeKernel(queue1, kernel1, …, NULL);
  clEnqueueNDRangeKernel(queue0, kernel0, …, NULL);
  clEnqueueNDRangeKernel(queue1, kernel1, …, NULL);
  …
}

一旦kernel0的第一个enqueue运行,编译器就可将kernel0的第二个enqueue列入硬件,无论kernel1状态如何。同样地,一旦kernel1的第一个enqueue运行,编译器就可将kernel1的第二个enqueue列入硬件,无论kernel0状态如何。

现在,请参考编译器无法将内核调用列入硬件的两个实例,如下:

实例1

int main()
{	…
  clEnqueueNDRangeKernel(queue, kernel0, …, NULL);
  clEnqueueNDRangeKernel(queue, kernel1, …, NULL);
  clEnqueueNDRangeKernel(queue, kernel0, …, NULL);
  …
}

由于队列是有序的,enqueue(入列)kernel1会阻止kernel0的第二个enqueue在硬件调用上排队。

实例 2

int main()
{	…
  clEnqueueNDRangeKernel(queue0, kernel0, …, NULL);
  clEnqueueNDRangeKernel(queue1, kernel1, …, &event);
  clFlush(queue1);
  clEnqueueNDRangeKernel(queue0, kernel0, …, 1, &event, NULL);
  …
}

因为kernel0的第二个enqueue正在等待kernel1上的enqueue完成,如果kernel1kernel0的第一个enqueue完成之前先完成执行,它只会在硬件内核调用队列中排队。

注意: 如果将 CL_PROFILING_COMMAND_END CL_PROFILING_COMMAND_START标记之间的clGetEventProfilingInfo()时间差异用于计算入队内核命令的执行时间,则该时间有可能为零,因为该时间在调用队列入队。请使用以下公式计算内核在多个队列中的平均执行时间: