clFlush(相对于clFinish)是否真的有任何作用?

5
OpenCL的clFinish()API调用会阻塞,直到命令队列上的所有命令都已完成执行。相关函数clFlush(),据说是将命令队列中所有先前排队的OpenCL命令发送到与命令队列相关联的设备上。
这是什么意思?它是否使这些命令跳过等待事件?这听起来不合理。它是否会阻塞,直到命令已被下发?可能不会,这就是clFinish()所做的。似乎clFlush()实际上不需要做任何事情。
我漏掉了什么?

2
在主机端排队大量的内核调用可能会消耗大量的RAM。clFlush可用于立即将当前队列提交到设备。请参见此链接 - doqtor
@doqtor:但是如果这种提交是可能的,为什么它还没有发生呢?即不将队列命令提交到设备并等待有什么好处?此外,如果有人排队了成千上万个内核,那么我可以百分之百确定他们正在错误地使用OpenCL(就像链接中的OP应该在一个副本的所有扫描中使用一个内核,或者在所有副本的一个扫描中使用一个内核)。 - einpoklum
clFinish不同,clFlush是异步的,这意味着您立即获得控制权,并且在GPU计算某些内容时可以同时执行其他操作。 - doqtor
@doqtor:我知道它是异步的,但我不明白为什么在调用它之前,clFlush可以做的任何事情都不应该已经发生了。 - einpoklum
clFlush/clFinish开始执行clEnqueueNDRangeKernel,它是一种懒加载方式(只有被添加到队列中才会执行)- 只有clFlush以非阻塞的方式执行。 - doqtor
@doqtor:当我在第一次调用clEnqueueNDRangeKernel时,那项工作应该已经开始了... - einpoklum
1个回答

4
当你使用opencl排队异步命令时,不能保证GPU实际执行这些命令。这些异步命令通常是设置为CL_FALSE的内存传输(clEnqueueWriteBuffer、clEnqueueReadBuffer)和内核调用(clEnqueueNDRangeKernel)。
如果你希望保证命令会执行,必须排队一个阻塞命令,例如将阻塞标志设置为CL_TRUE的API调用,或调用clFinish/clFlush。
clFlush基本上是将记录的命令传输到GPU。命令被“刷新”到硬件命令缓冲区,并在GPU调度程序安排它们执行时执行。
一个重要的特例是,当记录的命令(或先前在同一队列上记录的命令)必须与用户事件(clCreateUserEvent)或源自于记录到不同队列的命令的事件进行同步时,这些事件是通过OpenCL API提供的,并且可以阻塞刷新的命令,直到触发事件。
为什么需要clFlush?
背后的逻辑是,最有效的方法是通过许多排队调用来填充一个大的命令缓冲区,然后使用单个异步调用clFlush或其阻塞变体clFinish告诉GPU执行所有内容。
你可能想调用clFlush而不是clFinish的一个原因是,如果你希望交替执行CPU工作和GPU工作。
clEnqueue*** // async
clEnqueue*** // async
clEnqueue*** // async
clFlush(...); // async, make sure commands will execute
// do some heavy CPU work while GPU is executing commands
clFinish(...); // synchronous, ensure all commands are done, collect results.

一些 OpenCL 实现(例如 AMD)将在 clFlush 调用之间记录的命令分批执行。这意味着,从事件同步的角度来看,它将把记录的命令视为单个命令。

event1 = clEnqueue*** // async
event2 = clEnqueue*** // async
event3 = clEnqueue*** // async
clFlush(...); // async, previously recorded commands will execute
event4 = clEnqueue*** // async
event5 = clEnqueue*** // async
event6 = clEnqueue*** // async
clFlush(...); // async, previously recorded commands will execute
event3.wait(); 
// Do CPU work while the GPU processes kernels 4-6
event6.wait(); // wait for kernels 4-6

在这种情况下,第1至3个事件将在第三个内核执行完成后被标记,而第4至6个事件将在第六个内核执行完成后被标记。
这使您可以在GPU仍在工作时在CPU端进行结果的后处理。
另一个使用 clFlush 的用例是减少来自驱动程序工作和GPU调度的延迟。如果您的排队命令必须等待外部事件(障碍),您可以事先刷新它们到GPU。一旦触发了事件,命令已经被刷新到等待实际执行的硬件侧。从而节省了传输记录的命令到GPU所涉及的驱动程序延迟。

那么,从“软件缓冲区”转移到“硬件缓冲区”。但是,这不是自动发生的吗?例如,在CUDA中-没有这样的“刷新”操作。此外,如果硬件“命令缓冲区”不够大,则clFlush甚至不能保证它应该保证的内容。或者-也许会阻塞? - einpoklum
具体行为取决于实现方式。建议使用Codexl / Nsight等工具自行检查。从我的经验来看,即使对于单个内核,clflush也会产生影响。我不记得CUDA是否具有此功能,但其他API(如DirectX和OpenGL)肯定有。 - Elad Maimoni
@einpoklum 在这里查看备注部分 https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12commandqueue-executecommandlists。思路是相同的。 - Elad Maimoni
那个链接描述的更像是 clFinish() - einpoklum

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接