并行复制和opencl内核执行

时间:2015-05-05 15:03:49

标签: opencl

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

您是否认为通过将并行副本与OpenCL内核执行结合使用来解决此问题是否有意义?

例如,下面是我的方法:

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

致以最诚挚的问候,

2 个答案:

答案 0 :(得分:0)

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个执行事件,它们是用于阅读的等待事件。 在最后一部分中,我创建了另外两个用于阅读的事件,但它们并不是真正需要的,您可以使用阻塞读取。

答案 1 :(得分:0)

尝试使用乱序队列 - 大多数实现的硬件应该支持它们。您将要在内核中使用全局偏移量参数以及适用的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);