openCL作爲GPU編程的一種工具庫,編程方式 與CPU上不同,尤其是 線程同步。
GPU編程,爲了充分利用硬件特性,會開啓大量的計算線程,幾千甚至幾萬個邏輯線程。
對於一些複雜的計算過程,往往需要分步驟執行,即存在同步點。例如:A步驟1000個線程執行完畢後(同步點),再B步驟500個線程執行,執行完畢後(同步點),再執行C....等。
本人對不同的 同步方法進行了性能測試。
這裏假設讀者有一定的 OpenCL編程基礎。
測試任務爲:將兩個數組簡單計算後存儲。數組 a,b; 初始值全部 a=2,b=3; 計算 b=b+a*a; b=b*b; 正確結果爲全部 b=49;
這裏人爲的設定了一個 同步點。在步驟1: b=b+a*a 完成後同步一次,再執行步驟2: b=b*b;,並且步驟1 的執行,按數組順序執行由前往後。2的執行,按數組倒序執行,由後往前。
如果同步點,沒能正確的全局同步,那麼會出現,1,2同時執行,造成計算結果錯誤。
編程方式一:使用OpenCL的 barrier()同步。如下代碼:
max = 數組的長度 =500萬。也就是說測試中 opencl至少開啓 500萬個邏輯線程。
__kernel void Test01(
__global float* a
, __global float* b, int max)
{
int i = get_global_id(0);
if (i > max)
return;
b[i] = b[i] + a[i] * a[i]; //數組順序計算
barrier(CLK_GLOBAL_MEM_FENCE); //同步點
i = max - 1 - get_global_id(0) ; //數組倒序計算
b[i] = b[i] * b[i];
}
barrier 的特點:並不是全局同步。僅僅 group 內同步。一個500萬線程的任務,往往會分成 數千個 group。類似 線程,進程的關係。
而GPU硬件,真正同時執行的線程,並沒有500萬,可能也就 1024個 或者 2048個線程,同時執行。具體看GPU設備的規格。那麼實際執行過程,是一批一批的線程進行執行。具體執行流程這裏不作展開。總結的講,便是 barrier 同步 會出現,步驟1,2順序混亂,不能達到全局同步的效果。造成計算結果 錯誤。
編程方式二:拆分計算過程爲多個kernel。
__kernel void Test02(
__global float* a
, __global float* b, int max)
{
int i = get_global_id(0);
if (i > max)
return;
b[i] = b[i] + a[i] * a[i]; //順序計算
}
__kernel void Test03(
__global float* a
, __global float* b, int max)
{
int i = get_global_id(0);
if (i > max)
return;
i = max - 1 - get_global_id(0); //倒序計算
b[i] = b[i] * b[i];
}
在CPU端 創建2個opencl kernel :Test02,Test03,分別由 clEnqueueNDRangeKernel先後執行。通過設置 clEnqueueNDRangeKernel,或隊列執行順序,或用事件方式,便可以實現全局同步。保證步驟1,2的計算結果 正確。
要正確實現 opencl 全局同步,應適當拆分計算過程 爲多個 kernel 來分別執行。