OpenCL中的重叠传输和设备计算

8
我是一个OpenCL的初学者,对某些内容感到困惑。 我想要提高主机和设备之间图像传输的效率。 我画了一个示意图以更好地理解我的问题。
上方:现在的情况 | 下方:我想要的情况 HtD(主机到设备)和DtH(设备到主机)是内存传输。 K1和K2是内核。
我考虑使用映射内存,但第一次传输(从主机到设备)是通过clSetKernelArg()命令完成的,是吗? 还是我需要将输入图像分成子图像并使用映射来获取输出图像?
谢谢。
编辑:更多信息
K1处理内存输入图像。 K2处理K1的输出图像。
所以,我想将MemInput分成几部分给K1处理。 并且我想读取并保存由K2处理的MemOuput。

3
谢谢您对制作图表所使用的过程的肯定,我会为您进行翻译:对于你在制作图表时采用的过程给予一个“赞”(+1)。 - Hristo Iliev
4个回答

7
如您所见,您可以使用clEnqueueWriteBuffer等方法从主机到设备进行传输。
所有关键字中带有“enqueue”的命令都具有特殊属性:这些命令不会直接执行,只有在使用clFinishclFlushclEnqueueWaitForEvents、使用阻塞模式的clEnqueueWriteBuffer以及其他一些方式来触发它们时才会执行。
这意味着所有操作都同时发生,您必须使用事件对象进行同步。由于一切(可能)同时发生,因此您可以像这样做(每个点同时发生):
1. 传输数据A 2. 处理数据A并传输数据B 3. 处理数据B并传输数据C并检索数据A' 4. 处理数据C并检索数据B' 5. 检索数据C'
请记住:如果未使用事件对象将任务加入队列,则可能导致所有已加入队列的元素同时执行!
为了确保在处理B之前不会发生B的传输,您必须从clEnqueueWriteBuffer中检索事件对象,并将其作为要等待的对象提供给例如clEnqueueNDRangeKernel
cl_event evt;
clEnqueueWriteBuffer(... , bufferB , ... , ... , ... , bufferBdata , NULL , NULL , &evt);
clEnqueueNDRangeKernel(... , kernelB , ... , ... , ... , ... , 1 , &evt, NULL);

每个命令都可以等待特定的对象并生成一个新事件对象,而不是提供NULL。倒数第二个参数是一个数组,因此您可以等待多个事件!


编辑:总结以下评论 传输数据 - 哪个命令在哪里起作用?

       CPU                        GPU
                            BufA       BufB
array[] = {...}
clCreateBuffer()  ----->  [     ]              // 在GPU内存中创建(空)缓冲区*
clCreateBuffer()  ----->  [     ]    [     ]   // 在GPU内存中创建(空)缓冲区*
clWriteBuffer()   -arr->  [array]    [     ]   // 从CPU复制到GPU
clCopyBuffer()            [array] -> [array]   // 在GPU内部进行复制
clReadBuffer()    <-arr-  [array]    [array]   // 从GPU复制到CPU

* 您可以直接使用host_ptr参数提供数据来初始化缓冲区。


好的。但我的问题是如何传输缓冲区(在我这里是一个二维图像)。例如:在我的主机上,我有一个表示100个元素的数组的缓冲区。但我想每次发送10个元素到设备上,并且一旦完成,我同意同步以开始我的内核。我希望避免在开始时完全传输缓冲区(请参见方案)。 - Alex Placet
我刚刚写的两个参数中,有一个可以接收大小数组,另一个设置范围,另一个设置要复制的区域的偏移量。我还没有可用的文档,但我猜这正是你想要的!;) - Nippey
1
像这样:size_t origin[] = {0,0,0}, region[] = {2,2,1}; clEnqueueWriteImage(queue, image, CL_FALSE, origin, region, 0, 0, yourHostData, events_to_wait_for, new_event) 现在改变原点并再次调用它。这样你就可以传输缓冲区的子集。确保适当地偏移指向 yourHostData 的指针。(注意:原点和偏移必须是三维的。对于二维图像,region[2] 必须为 1 - Nippey
你不能使用之前图像的 cl_mem 对象作为 yourHostData?我尝试这样做时出现了错误。那么我该如何偏移该指针?error = clEnqueueWriteImage( commandQueue, input_image, CL_TRUE, origin1, region1, image_row_pitch, image_slice_pitch, loaded_image, 0, NULL, NULL ); loaded_image 是一个 cl_mem image-2D。 - Alex Placet
1
抱歉,我没有完全理解这个。我的工作流程:**-#-** 从硬盘读取图像到[integer|char]-数组 -#- 在GPU上创建缓冲区 -#- 将数组传输到缓冲区(如果需要,可以逐部分完成此操作)**-#-** 处理它。 - Nippey
显示剩余9条评论

3
许多OpenCL平台不支持乱序命令队列;大多数供应商建议使用多个(按顺序)命令队列来执行重叠的DMA和计算。您可以使用事件来确保依赖关系以正确顺序完成。 NVIDIA有示例代码展示了这种方式执行重叠的DMA和计算(虽然不是最优的,它可以比他们所说的速度略快)。

感谢您的回答。 在我的情况下,OpenCL内核计算很短。数据传输需要很长时间,这就是我想节省时间的地方。 我尝试在NVIDIA GTX 260上进行操作,但是该图形卡与数据传输/传输重叠不兼容(仅与计算/数据传输重叠兼容)。在NVIDIA OpenCL最佳实践指南中,我们可以读到:
NVIDIA设备的计算能力>=2.0具有2个独立的复制引擎,并且能够与设备计算同时进行2个方向的并行复制。
而GTX 260的计算能力=1.3...
- Alex Placet
Dithermaster,您能指出一个实现您所提到的方法的可行示例吗? - user3116936
请查看此页面上的“OpenCL Overlapped Copy/Compute Sample”示例:https://developer.nvidia.com/opencl - Dithermaster

2
当您创建命令队列时,需要在属性中启用乱序执行。请参阅:CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE,clCreateCommandQueue
这将允许您设置较小的任务链并将它们链接在一起。所有这些都是在主机上完成的。
主机伪代码:
for i in taskChainList
  enqueueWriteDataFromHost
  enqueueKernel(K1)
  enqueueKernel(K2)
  enqueueReadFromDevice
clfinish

当您排队任务时,请将先前的cl_event放入每个任务的event_wait_list中。我上面提到的“enqueueWriteDataFromHost”不需要等待另一个事件才能开始。

或者,

cl_event prevWriteEvent;
cl_event newWriteEvent;
for i in taskChainList
  enqueueWriteDataFromHost // pass *prevWriteEvent as the event_wait_list, and update with newWriteEvent that the enqueue function produces. Now each Write will wait on the one before it.
  enqueueKernel(K1)
  enqueueKernel(K2)
  enqueueReadFromDevice  //The reads shouldn't come back out of order, but they could (if the last block of processing were much faster then the 2nd-last for example)
clfinish

2

恰当的方式(我这样做并且完美运行)是创建2个命令队列,一个用于I/O,另一个用于处理。两者必须位于同一上下文中。

您可以使用事件来控制两个队列的调度,并且操作将并行执行(如果可以)。即使设备不支持outoforderqueue,它确实可以正常工作。

例如,您可以在I / O队列中将所有100个图像排队到GPU并获取其事件。然后将此事件设置为内核的触发器。而D到H传输则由内核事件触发。

即使您将所有这些任务同时入队,它们也将按顺序且具有并行I/O进行处理。


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