kernel 本质上是一个for 循环,其中每次迭代并行运行。确切的执行顺序是随机的,因此从一次迭代到下一次迭代不能有任何数据依赖关系;否则你必须使用双缓冲区(只从一个缓冲区读取,只写入另一个缓冲区)。
在您的情况下,内核将读取:
__kernel void adjacentCopy(const __global double *a, __global double *b, const unsigned int N) {
int gid = get_global_id(0);
if(gid==0||gid==N-1) return; // guard clause: do not execute the first and last element
b[gid] = a[gid-1]+a[gid]+a[gid+1]; // double buffers to resolve data dependencies: only read from a and only write to b
}
对于极端情况gid==0||gid==N-1,在这样的计算网格上,您通常使用周期性边界条件。然后内核将变得无分支,如下所示:
__kernel void adjacentCopy(const __global double *a, __global double *b, const unsigned int N) {
int gid = get_global_id(0);
b[gid] = a[(gid+N-1)%N]+a[gid]+a[(gid+1)%N]; // periodic boundaries with modulo; in "(gid+N-1)" the "+N" ensures that the argument of the modulo operator always is positive
}
现在对于local 内存优化:没有它,对于每个线程,您从慢速global 内存中读取a 的3 个相邻值。理论上,每个线程只能从global 内存加载一个元素,并使用快速local 内存在工作组内共享数据。但是gid==0||gid==N-1 的两个线程必须从global 内存中加载2 个值,从而引入分支,这可能会扼杀任何潜在的性能提升。在这种情况下,增加的复杂性以及没有显着的性能提升使得local 内存优化成为不利的选择。这就是内核的样子:
#define def_workgroup_size 128 // set this to the size of the workgroup
__kernel void adjacentCopy(const __global double *a, __global double *b, const unsigned int N) {
int gid = get_global_id(0);
int lid = get_local_id(0);
__local double cached_a[def_workgroup_size+2]; // as large as the workgroup, plus neighbors on the left and right sides of the workgroup
if(lid==0) cached_a[lid] = a[(gid+N-1)%N]; // first thread in workgroup also has to load left neighbor
cached_a[lid+1] = a[gid];
if(lid==def_workgroup_size-1) cached_a[lid+1] = a[(gid+1)%N]; // last thread in workgroup also has to load right neighbor
barrier(CLK_LOCAL_MEM_FENCE); // barrier to make sure cached_a is entirely filled up
b[gid] = cached_a[lid]+cached_a[lid+1]+cached_a[lid+2]; // read 3 values from local memory
}