OpenCL:循环内核?

时间:2018-12-28 20:29:46

标签: c++ performance queue kernel opencl

我正在运行一个OpenCL内核,该内核一遍又一遍地处理和重新处理相同的数据集(这是一个迭代的物理求解器)。

在我的测试中,调用clEnqueueNDRangeKernel花费不菲。例如,当运行模拟的1000个子步骤(要求对clEnqueueNDRangeKernel进行1000次相同的调用以处理相同的数据)时,似乎对clEnqueueNDRangeKernel的这些调用实际上成为了瓶颈。我的(伪)代码如下所示:

[create buffers]
[set kernel arguments]

for (int i = 0; i < 1000; i++) //queuing the kernels takes a while
{
    clEnqueueNDRangeKernel(queue, kernel, args...); 
}

clFinish(queue); //waiting for the queue to complete doesn't take much time
[read buffers]

我了解到,对clEnqeueuNDRangeKernel的首次调用将初始化对GPU的所有延迟缓冲区传输...因此,第一次调用可能会产生额外的费用。但是,在我的测试中,10次迭代的循环比1000次迭代的速度快得多,这使我相信数据传输不是瓶颈。

我也给clEnqueueNDRangeKernel非阻塞的印象是,直到内核完成,它才会阻塞,因此内核的复杂性不应该成为瓶颈(在我的情况下,内核在调用clFinish()之前,执行不应阻塞。

但是,当我分析我的代码时,大部分时间只花在处理clFinish()之前的for循环上……所以看来内核本身的排队是花费最多的时间在这里。

我的问题:是否可以告诉GPU重新运行先前排队的内核N次,而不必手动将内核排队N次?在我的情况下,每次迭代都不需要更改或更新内核的参数……只需重新运行内核。重复调用它可以提高效率吗?

1 个答案:

答案 0 :(得分:0)

OpenCL 2.x支持动态并行性,该动态并行性允许1个工作项启动新内核。如果每个内核启动不需要任何gpu-cpu数据传输,则可以有1个工作项启动1000个内核,并等待每个内核在该工作项之前完成。使用事件使所有子内核依次运行。

在OpenCL 1.2中,您可以使用原子和循环来执行“运行中线程”内核同步,但这不会比新内核启动imo快,并且它不是可移植的同步方式。

如果每次内核启动花费的时间比每次内核运行花费的时间更多,则说明GPU上的工作量不足。您可以在GPU上简单地执行c = a + b,而这仅仅是因为在gpu管道上进行内核调度比执行c = a + b需要更多时间而不够快。

但是,您仍然可以使用clEnqueueWaitForEvents和顺序命令队列执行以下方法:

thread-0:
    enqueue user event, not triggered
    enqueue 1000 kernels, they don't start yet because of untriggered wait
thread-1:
    nothing

下一步骤:

thread-0:
    enqueue new user event on a new command queue, not triggered
    enqueue 1000 kernels on new command queue so they don't start yet
thread-1:
    run the old command queue from last timestep by triggering the user event

,以便排队和运行至少可以“重叠”。如果您需要更多队列来运行重叠率,

thread-0 to thread-N-2:
    enqueue new user event on a new command queue, not triggered
    enqueue 1000 kernels on new command queue so they don't start yet
thread-N-1:
    iterate all command queues
          run currently selected command queue from last timestep  by triggering the user event

现在入队速度提高了N-1倍,仅在GPU端调度开销上运行它们。如果您在GPU端的调度开销很大(1M个工作项对应1M c = a + b个计算),则每个工作项应做更多的工作。

在7个线程产生等待用户自己的用户事件触发的填充命令队列和8个线程通过触发它们消耗它们的线程时,使生产者-消费者风格的内核启动更好。即使他们需要将下载数据上传到GPU或从GPU上传数据,也可以使用。

即使是HD7870之类的老式GPU也可以同时支持32个以上的命令队列(每个GPU),因此您可以通过高端CPU扩展排队性能。

如果pci-e桥(提升板的高延迟?)造成了瓶颈,则OpenCL2.x动态并行性必须优于CPU端的生产者-消费者模式。