并行复制和opencl内核执行

并行复制和opencl内核执行,opencl,Opencl,我想使用OpenCL实现一个图像过滤算法,但是图像大小非常大(4096 x 4096)。我知道复制到OpenCL设备的时间可能太长 您认为通过结合使用并行拷贝和OpenCL内核执行来解决这个问题有意义吗 例如,以下是我的方法: 1) 将完整图像拆分为两部分。 2) 将前半部分复制到设备。 3) 在设备上执行图像过滤内核,然后将图像的后半部分复制到设备。 4) 阻止内核执行直到前半部分完成,然后再次调用内核以处理第二部分。 5) 阻塞,直到第二部分完成 请注意,OpenCL线程的执行完全独立于您的

我想使用OpenCL实现一个图像过滤算法,但是图像大小非常大(4096 x 4096)。我知道复制到OpenCL设备的时间可能太长

您认为通过结合使用并行拷贝和OpenCL内核执行来解决这个问题有意义吗

例如,以下是我的方法:

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


请注意,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个队列(仅在创建时!)
MQUEIO=cl::CommandQueue(上下文,设备[0],0);
mQueueRun=cl::CommandQueue(上下文,设备[0],0);
//每次运行图像过滤器时
//对2次写入进行排队
cl::事件wev1//事件,以便在写入完成时知道
mqueio.enqueueWriteBuffer(ImageBufferCL,CL_FALSE,0,size/2,imageCPU,NULL,&wev1);
cl::事件wev2//事件,以便在写入完成时知道
mqueio.enqueueWriteBuffer(ImageBufferCL,CL_FALSE,size/2,size/2,imageCPU+size/2,&wev2);
//对2次运行进行排队(具有适当的相关性)
向量等待;
等等,向后推(wev1);
cl::事件ev1//事件来跟踪运行命令的完成
enqueueNDRangeKernel(kernel,cl::NDRange(0),cl::NDRange(size/2),cl::NDRange(localsize),&wait,&ev1);
等待[0]=wev2;
cl::事件ev2//事件来跟踪运行命令的完成
enqueueNDRangeKernel(kernel,cl::NDRange(size/2),cl::NDRange(size/2),cl::NDRange(localsize),&wait,&ev2);
//完成后读回数据
标准:矢量版本(2);
等待[0]=ev1;
eQueueReadBuffer(ImageBufferCL,CL_FALSE,0,size/2,imageCPU,&wait,&rev[0]);
等待[0]=ev1;
mqueio.enqueueReadBuffer(ImageBufferCL,CL_FALSE,size/2,size/2,imageCPU+size/2,&wait,&rev[1]);
rev[0]。等待();
rev[1]。等待();
注意我是如何创建两个用于写入的事件的,它们是执行的等待事件;和2个用于执行的事件,它们是用于读取的等待事件。
在最后一部分中,我创建了另外两个用于读取的事件,但实际上并不需要它们,您可以使用阻塞读取。

尝试使用无序队列-大多数实现的硬件都应该支持它们。您需要在内核中使用global offset参数以及global_id(如果适用)。在某种程度上,像这样的分割策略会使你得到递减的回报,但应该存在一个数字,这样你就可以在延迟减少中获得好的回报-我猜在[2100]中可能是暴力行为的一个很好的间隔。请注意,一次只能有一个内核写入内存缓冲区,并确保输入缓冲区为const(只读)。请注意,您还必须将一个内核中N个缓冲区拆分的结果合并到一个输出-这意味着您将有效地将所有像素写入GDS两次。如果您能够使用OpenCL2.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);
cl::CommandQueue queue(上下文、设备、cl_queue_OUT_OF_ORDER_EXEC_MODE|u ENABLE|cl_queue_ON_device);
cl::事件最后一个事件;
向量事件;
std::矢量输出_缓冲区//不管有多少个拆分,都要初始化,确保至少有足够的内存用于写入内容,并更新内核,使其只写入相对区域。
//您可以通过更多的拆分来实现更精细的粒度
//只需确保内核正在使用全局偏移量-
//在这种情况下,将此代码调整为循环
设置参数(内核、图像输入、图像输出[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]),以及事件和最后一个_事件);事件。推回(最后一个事件);
设置参数(内核、图像输入、图像输出[0]);
queue.enqueueNDRangeKernel(kernel,cl::NDRange(0,size/2*本地大小),cl::NDRange(cols*本地大小[0],(size-size/2)*本地大小[1]),cl::NDRange(本地大小[0],本地大小[1]),事件和最后一个事件);事件。推回(最后一个事件);
设置参数(合并缓冲区、内核、输出缓冲区…)
queue.enqueueNDRangeKernel(merge\u buffers\u kernel,NDRange(),NDRange(cols*local\u size[0],rows*local\u size[1])
cl::waitForEvents(事件);

不要麻烦处理无序的命令队列;大多数实现都不支持它们。重叠计算和DMA的方法是使用多个命令队列和事件进行同步。@Dithermaster在主机端是这样,但如果硬件本身能够支持,设备端队列往往会得到支持(例如,AMD中的许多异步计算引擎)-据我所知,唯一不支持它的官方线路是Altera的。这需要更好的文档,但Intel、AMD和Nvidia应该让它工作,并且有这么大的图像,如果他使用其中一个,我不会感到惊讶。从我的观点来看,OoO队列和多个队列是一样的。无论如何都必须使用事件是的。那么,为什么要把所有东西都放在一个队列中,冒着实现限制速度的风险,而你只能在2个队列中使用它呢?顺便说一句,上次我使用nVIDIA OpenCL时,它支持的顺序不正确,但它不允许2个任务同时运行,所以内核和副本没有重叠。@DarkZeros这是装备