2015-05-05 106 views
0

我想實現一個使用OpenCL的圖像過濾算法,但圖像大小非常大(4096 x 4096)。我瞭解到OpenCL設備的複製時間可能會過長。並行拷貝和opencl內核執行

您認爲通過結合使用並行副本和OpenCL內核執行來解決此問題是否有意義?

例如,下面是我的方法:

1)拆分全圖像分成2份。 2)將前半部分複製到設備。 3)在設備上執行圖像過濾內核,然後將圖像的後半部分複製到設備上。 4)阻止內核執行直到前半部分完成,然後再次調用內核來處理第2部分。 5)直到第2部分結束。

此致敬禮

+0

您能否添加一些平臺和硬件的詳細信息?設備和圖像之間的PCIe鏈接速度如何?另外,AMD APU可以完全避免拷貝,儘管它的計算單元更少 - 一些嵌入式芯片也可以像APU那樣很好地集成。 –

回答

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個用於執行的事件,這些是等待讀取的事件。 在最後一部分我創建了另外2個閱讀事件,但它們並不是真的需要,你可以使用阻塞閱讀。

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); 
+0

不要打擾亂序指令隊列;大多數實現不支持它們。重疊計算和DMA的方法是使用多個命令隊列和事件進行同步。 – Dithermaster

+0

@Dithermaster這在主機方面是正確的,但如果硬件本身可以支持它(例如AMD中的許多異步計算引擎),設備端隊列往往會得到支持 - 我知道的唯一不支持的官方線路是Altera的。這是需要更好的文檔,但英特爾,AMD和Nvidia應該有它的工作和一個巨大的形象,如果他使用其中的一個,我不會感到驚訝。 –

+0

從我的角度來看,OoO隊列和多個隊列是一樣的。無論如何都必須使用事件。那麼爲什麼要把所有東西都放在一個單一的隊列中,並且有可能在實現時限制速度,這可以在2秒內完成。順便說一句,上次我使用nVIDIA OpenCL它支持亂序,但它不允許2個任務同時運行,所以內核和副本不重疊。 – DarkZeros