我计算了 N 个粒子在其引力场中移动的轨迹。我编写了以下 OpenCL 内核:
#define G 100.0f
#define EPS 1.0f
float2 f (float2 r_me, __constant float *m, __global float2 *r, size_t s, size_t n)
{
size_t i;
float2 res = (0.0f, 0.0f);
for (i=1; i<n; i++) {
size_t idx = i;
// size_t idx = (i + s) % n;
float2 dir = r[idx] - r_me;
float dist = length (dir);
res += G*m[idx]/pown(dist + EPS, 3) * dir;
}
return res;
}
__kernel void take_step_rk2 (__constant float *m,
__global float2 *r,
__global float2 *v,
float delta)
{
size_t n = get_global_size(0);
size_t s = get_global_id(0);
float2 mv = f(r[s], m, r, s, n);
float2 mr = v[s];
float2 vpred1 = v[s] + mv * delta;
float2 rpred1 = r[s] + mr * delta;
float2 nv = f(rpred1, m, r, s, n);
float2 nr = vpred1;
barrier (CLK_GLOBAL_MEM_FENCE);
r[s] += (mr + nr) * delta / 2;
v[s] += (mv + nv) * delta / 2;
}
然后我多次运行这个内核作为全局工作大小=[体数]的一维问题:
void take_step (struct cl_state *state)
{
size_t n = state->nbodies;
clEnqueueNDRangeKernel (state->queue, state->step, 1, NULL, &n, NULL, 0, NULL, NULL);
clFinish (state->queue);
}
这是来自AMD OpenCL 优化指南(2015 年)的引述:
在某些情况下,通道冲突的一个意外情况是从同一地址读取是一个冲突,即使在 FastPath 上也是如此。这不会发生在只读存储器上,例如常量缓冲区、纹理或着色器资源视图 (SRV);但可以在读/写 UAV 内存或 OpenCL 全局内存上。
我队列中的工作项都尝试在此循环中访问相同的内存,因此肯定存在通道冲突:
for (i=1; i<n; i++) {
size_t idx = i;
// size_t idx = (i + s) % n;
float2 dir = r[idx] - r_me;
float dist = length (dir);
res += G*m[idx]/pown(dist + EPS, 3) * dir;
}
我换了
size_t idx = i;
// size_t idx = (i + s) % n;
和
// size_t idx = i;
size_t idx = (i + s) % n;
所以第一个工作项(具有全局 id 0
)首先访问数组r
中的第一个元素,第二个工作项访问第二个元素,依此类推。
我预计这种变化一定会导致性能提高,但相反,它会导致性能显着下降(大约是 2 倍)。我错过了什么?为什么在这种情况下,完全相同的内存可以更好地访问它?
如果您有其他提示如何提高性能,请与我分享。OpenCL 优化指南非常混乱。