本篇教程中,我們學習一下如何用opencl有效實現數組求和,也就是通常所說的reduction問題。
在程序中,我們設置workgroup size為256,kernel的輸入、輸出緩沖參數都用uint4的格式,這樣我們原始求和的數組大小為256*4的倍數,數據類型為uint。我們設定每個workgroup處理處理512個uint4,即2048個uint
為了簡便期間,我們輸出數組長度定為4096,即需要2個workgruop來處理。
kernel代碼如下:
__kernel void reduce(__global uint4* input, __global uint4* output, __local uint4* sdata)
{
// 把數據裝入lds
unsigned int tid = get_local_id(0);
unsigned int bid = get_group_id(0);
unsigned int gid = get_global_id(0);
unsigned int localSize = get_local_size(0);
unsigned int stride = gid * 2;
sdata[tid] = input[stride] + input[stride + 1];
barrier(CLK_LOCAL_MEM_FENCE);
// 在lds中進行reduction操作,得到數組求和的結果
for(unsigned int s = localSize >> 1; s > 0; s >>= 1)
{
if(tid < s)
{
sdata[tid] += sdata[tid + s];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
// 把一個workgroup計算的結果輸出到輸出緩沖,是一個uint4,還需要在host端再進行一次reduction過程
if(tid == 0) output[bid] = sdata[0];
}
在程序中,global和local的NDRange,我們都用一維的形式。下面以圖的方式看下kernel代碼是如何執行的:
對第一個workgroup中的第一個thread的來說,它首先進行一次reduction操作,把兩個uint4相加,放到lds(shared memory)中,然后再在lds中進行reduction操作,此時要從global memory中取數據,可以看出連續的thread訪問連續的global memory,這時可以利用合並讀寫。
申請的shared memory大小為groupsize*sizeof(uint4),相加后uint4放入32bank的lds中,放置的方式應該是如下圖所示,因為放入的是uint4,所以會放入連續的4個bank中(每個bank都是dword寬),可見只能同時有8個thread訪問lds,所以會有一定程序的bank conflit。從App profiler session,我們可以看到:
接下來,kernel會通過一個for循環迭代執行reduction操作,求得一個workgroup中的uint4的和。
迭代的第一次s=128,這時會執行如下圖的兩兩相加,workgroup中同時執行的thread為128,thread local id大於等於128的線程都不會做什么事情,在每個循環的末尾,有一個barrier來同步所有thread,以便所有thread都完成這次循環后再進入下一次循環。
第二次迭代的時候,只剩下前面128個uint4,workgroup中同時執行的thread為64。最后,當s=1時候,完成迭代reduction操作,然后把thread0(第一個thread)的結果輸出。
在host段,我們還要做一次相加操作,把不同workgroup得到的uint4,拆分成uint,並相加求得最終的結果。
//在cpu reduction各個workgroup的結果以及uint4分量 reduction
output = 0;
for(int i = 0; i < numBlocks * VECTOR_SIZE; ++i)
output += outMapPtr[i];
printf("gpu reduction result:%d\n", output);
if(refOutput==output) printf("passed\n");
程序執行后結果如下:
完整的代碼請參考:
工程文件gclTutorial11
代碼下載:
稍后提供