【问题标题】:Parallel copy and opencl kernel execution并行复制和opencl内核执行
【发布时间】:2015-05-07 08:32:26
【问题描述】:

我想使用 OpenCL 实现图像过滤算法,但图像尺寸非常大 (4096 x 4096)。我了解复制到 OpenCL 设备的时间可能会过长。

您认为将并行副本与 OpenCL 内核执行结合使用来解决这个问题有意义吗?

例如,以下是我的方法:

1) 将整个图像分成两部分。 2) 将前半部分复制到设备中。 3) 在设备上执行图像过滤内核,然后将图像的第二半复制到设备上。 4)阻塞内核执行直到前半部分完成,然后再次调用内核处理第二部分。 5) 阻塞直到第二部分完成。

最好的问候,

【问题讨论】:

  • 你能补充一些平台和硬件细节吗?您的设备和映像之间的 PCIe 链接速度是多少(如果有)?此外,AMD APU 可以完全避免复制,尽管它的计算单元较少 - 一些嵌入式芯片也像 APU 一样具有很好的集成性。

标签: opencl


【解决方案1】:

OpenCL 线程的执行完全独立于您的应用程序。因此,无需在每次通话后“等待”。只需将所有订单刷新到 OpenCL,它就会正确安排它们。

只需要有 2 个队列,以便能够并行运行命令。所以你需要一个 IO 队列和一个执行队列。单个队列(即使在乱序模式下)永远不能并行运行 2 个操作。

这里有一个事件方法示例,您可以在完成入队后立即在队列上调用 clFlush() 以加快它们的速度。

//Create 2 queues (at creation only!)
mQueueIO = cl::CommandQueue(context, device[0], 0);
mQueueRun = cl::CommandQueue(context, device[0], 0);


//Everytime you run your image filter
//Queue the 2 writes
cl::Event wev1; //Event to known when the write finishes
mQueueIO.enqueueWriteBuffer(ImageBufferCL, CL_FALSE, 0, size/2, imageCPU, NULL, &wev1);
cl::Event wev2; //Event to known when the write finishes
mQueueIO.enqueueWriteBuffer(ImageBufferCL, CL_FALSE, size/2, size/2, imageCPU+size/2, &wev2);


//Queue the 2 runs (with the proper dependency)
std::vector<cl::Event> wait; 
wait.push_back(wev1);
cl::Event ev1; //Event to track the finish of the run command
mQueueRun.enqueueNDRangeKernel(kernel, cl::NDRange(0), cl::NDRange(size/2), cl::NDRange(localsize), &wait, &ev1);
wait[0] = wev2;
cl::Event ev2; //Event to track the finish of the run command
mQueueRun.enqueueNDRangeKernel(kernel, cl::NDRange(size/2), cl::NDRange(size/2), cl::NDRange(localsize), &wait, &ev2);


//Read back the data when it has finished
std::vector<cl::Event> rev(2); 
wait[0] = ev1;
mQueueIO.enqueueReadBuffer(ImageBufferCL, CL_FALSE, 0, size/2, imageCPU, &wait, &rev[0]);
wait[0] = ev1;
mQueueIO.enqueueReadBuffer(ImageBufferCL, CL_FALSE, size/2, size/2, imageCPU + size/2, &wait, &rev[1]);
rev[0].wait();
rev[1].wait();

请注意我是如何创建 2 个用于写入的事件,这些是执行的等待事件;和 2 个执行事件,即读取的等待事件。 在最后一部分中,我创建了另外 2 个用于读取的事件,但它们并不是真正需要的,您可以使用阻塞读取。

【讨论】:

    【解决方案2】:

    尝试使用乱序队列 - 大多数实现的硬件应该支持它们。您需要在内核中使用全局偏移参数以及适用的 global_id。在某些时候,您将使用这样的除法策略获得递减收益,但应该存在一个数字,以便您可以在减少延迟方面获得良好的回报 - 我猜它在 [2, 100] 中可能是蛮力的一个很好的间隔轮廓。请注意,一次只有一个内核可以写入内存缓冲区,并确保输入缓冲区为 const(只读)。请注意,您还必须将一个内核中 N 个缓冲区拆分的结果合并到一个输出中 - 这意味着您将有效地将所有像素两次写入 GDS。 OpenCL 2.0 或许可以用它的图像类型为我们保存所有这些分裂的写入,如果你能够使用它的话。

    cl::CommandQueue queue(context, device, CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE|CL_QUEUE_ON_DEVICE);
    
    cl::Event last_event;
    std::vector<Event> events;
    std::vector<cl::Buffer> output_buffers;//initialize with however many splits you have, ensure there is at least enough for what is written and update the kernel perhaps to only write to it's relative region.
    
    //you might approach finer granularity with even more splits
    //just make sure the kernel is using the global offset - 
    //in which case adjust this code into a loop
    set_args(kernel, image_input, image_outputs[0]);
    queue.enqueueNDRangeKernel(kernel, cl::NDRange(0, 0), cl::NDRange(cols * local_size[0], (rows/2) * local_size[0]), cl::NDRange(local_size[0], local_size[1]), &events, &last_event); events.push_back(last_event);
    set_args(kernel, image_input, image_outputs[0]);
    queue.enqueueNDRangeKernel(kernel, cl::NDRange(0, size/2 * local_size), cl::NDRange(cols * local_size[0], (size - size/2) * local_size[1]), cl::NDRange(local_size[0], local_size[1]), &events, &last_event); events.push_back(last_event);
    
    set_args(merge_buffers_kernel, output_buffers...)
    queue.enqueueNDRangeKernel(merge_buffers_kernel, NDRange(), NDRange(cols * local_size[0], rows * local_size[1])
    cl::waitForEvents(events);
    

    【讨论】:

    • 不要理会乱序的命令队列;大多数实现不支持它们。重叠计算和DMA的方法是使用多个命令队列和事件来同步。
    • @Dithermaster 这在主机端是正确的,但如果硬件本身可以支持它(例如 AMD 中的许多异步计算引擎),设备端队列往往会得到支持——我知道的唯一官方线路不支持它的是Altera的。这需要更好的文档,但英特尔、AMD 和 Nvidia 应该可以让它工作,并且图像那么大,如果他使用其中之一,我不会感到惊讶。
    • 在我看来,OoO 队列和倍数队列是一样的。无论如何都必须使用事件。那么,为什么将所有内容都放在一个队列中,并冒着实施限制速度的风险呢?顺便说一句,上次我使用 nVIDIA OpenCL 时,它支持乱序但它不允许 2 个任务同时运行,因此内核和副本没有重叠。
    • @DarkZeros 它是工作 [tm] 的正确工具,我们应该在此前提下使用它。在 NVidia 上,这是硬件的限制,您可能会面临与多个队列/CUDA 流相同的限制 - 如果您查看最近的硬件,它确实明确表示它在 clinfo 中支持它,并且如果硬件支持并发内核执行,它应该可以工作 - 如果它没有提交错误,因为他们似乎关心其实现中的错误。您也可以使用多个队列,但这比仅正确使用乱序队列和候补名单更难跨基础架构管理。
    猜你喜欢
    • 2019-01-16
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-28
    相关资源
    最近更新 更多