我有两个版本的内核执行相同的任务 - 填充链接的单元格列表 - 两个内核之间的差异是存储粒子位置的数据类型,第一个使用浮点数组来存储位置(4浮点数)每个粒子由于128位读/写),第二个使用vec3f结构数组来存储位置(一个容纳3个浮点数的结构)。
使用nvprof进行一些测试,我发现第二个内核(使用vec3f)运行速度比第一个快:
Time(%) Time Calls Avg Min Max Name
42.88 37.26s 2 18.63s 23.97us 37.26s adentu_grid_cuda_filling_kernel(int*, int*, int*, float*, int, _vec3f, _vec3f, _vec3i)
11.00 3.93s 2 1.97s 25.00us 3.93s adentu_grid_cuda_filling_kernel(int*, int*, int*, _vec3f*, int, _vec3f, _vec3f, _vec3i)
尝试使用256和512000粒子填充链接的单元格列表进行测试。
我的问题是,这里发生了什么?我认为浮点数组应该由于合并内存而进行更好的内存访问,而不是使用具有未对齐内存的vec3f结构数组。我错过了什么?
这些是内核,第一个内核:
__global__ void adentu_grid_cuda_filling_kernel (int *head,
int *linked,
int *cellnAtoms,
float *pos,
int nAtoms,
vec3f origin,
vec3f h,
vec3i nCell)
{
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx >= nAtoms)
return;
vec3i cell;
vec3f _pos = (vec3f){(float)pos[idx*4+0], (float)pos[idx*4+1], (float)pos[idx*4+2]};
cell.x = floor ((_pos.x - origin.x)/h.x);
cell.y = floor ((_pos.y - origin.y)/h.y);
cell.z = floor ((_pos.z - origin.z)/h.z);
int c = nCell.x * nCell.y * cell.z + nCell.x * cell.y + cell.x;
int i;
if (atomicCAS (&head[c], -1, idx) != -1){
i = head[c];
while (atomicCAS (&linked[i], -1, idx) != -1)
i = linked[i];
}
atomicAdd (&cellnAtoms[c], 1);
}
这是第二个内核:
__global__ void adentu_grid_cuda_filling_kernel (int *head,
int *linked,
int *cellNAtoms,
vec3f *pos,
int nAtoms,
vec3f origin,
vec3f h,
vec3i nCell)
{
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx >= nAtoms)
return;
vec3i cell;
vec3f _pos = pos[idx];
cell.x = floor ((_pos.x - origin.x)/h.x);
cell.y = floor ((_pos.y - origin.y)/h.y);
cell.z = floor ((_pos.z - origin.z)/h.z);
int c = nCell.x * nCell.y * cell.z + nCell.x * cell.y + cell.x;
int i;
if (atomicCAS (&head[c], -1, idx) != -1){
i = head[c];
while (atomicCAS (&linked[i], -1, idx) != -1)
i = linked[i];
}
atomicAdd (&cellNAtoms[c], 1);
}
这是vec3f结构:
typedef struct _vec3f {float x, y, z} vec3f;
答案 0 :(得分:4)
这不是AoS vs. SoA的一个例子。让我们看看重要的代码行和隐含在其中的数据结构。
你的第一个," SoA"或者"慢"情况下:
vec3f _pos = (vec3f){(float)pos[idx*4+0], (float)pos[idx*4+1], (float)pos[idx*4+2]};
^ ^ ^
| | |
These values are stored in *adjacent* memory locations
因此,一个单独的线程正在连续访问pos[idx*4]
以及紧随其后的2个位置。这就是结构的存储方式!你调用Arrays结构实际上是一个结构数组,它存储在内存中的方式。拥有有效的" SoA"例如,您的代码需要看起来像这样:
vec3f _pos = (vec3f){(float)pos1[idx], (float)pos2[idx], (float)pos3[idx]};
^
|
Adjacent threads will read adjacent values for pos1, pos2, and pos3
leading to *coalesced* access.
你的" AoS"或者"快速"并没有真正的存储格式。
答案 1 :(得分:1)
在我看来,你的两种方法实际上都是AoS,唯一的区别是第一种方法是具有四种元素结构的AoS,而第二种方法仅使用三种元素。这就是为什么你的第二个解决方案更可取的原因。
如果您真的想在第一个解决方案中使用SoA,则需要按如下方式组织pos数组:
vec3f _pos = (vec3f){(float)pos[idx], (float)pos[N + idx], (float)pos[2 * N + idx]};