假设我们有3个整数的struct
未对齐:
struct data {
int x;
int y;
int z;
};
我将此struct
的数组传递给内核。我知道我应该传递数组的结构而不是结构数组,但这对于这个问题并不重要。
warp中的32个线程,以合并方式访问内存(i到i + 31),等于384字节的总内存。 384字节是L1高速缓存行的倍数(128字节),这意味着每个128字节的三个存储器事务。
现在,如果我们有一个对齐的struct
:
struct __align__(16) aligned_data {
int x;
int y;
int z;
};
如果访问模式与前面的示例保持一致,那么它将获取512字节的内存,这是4个内存事务,每个请求128字节。
因此,这意味着第一个示例更高效,或者第二个示例仍然更有效,尽管它获取更多内存。
答案 0 :(得分:4)
回答问题的唯一真正方法是通过基准测试。如果你这样做,你可能无法得到相同的答案,具体取决于你的硬件。当我运行时:
#define NITER (128)
struct data {
int x;
int y;
int z;
};
struct __align__(16) aligned_data {
int x;
int y;
int z;
};
template<typename T, int niter>
__global__
void kernel(T *in, int *out, int dowrite=0)
{
int tid = threadIdx.x + blockIdx.x * blockDim.x;
int nthreads = blockDim.x * gridDim.x;
int oval = 0;
#pragma unroll
for(int i=0; i<niter; ++i,tid+=nthreads) {
T val = in[tid];
oval += val.x + val.y + val.z;
}
if (dowrite) {
out[tid] = oval;
}
}
template __global__ void kernel<data,NITER>(data *, int*, int);
template __global__ void kernel<aligned_data,NITER>(aligned_data *, int*, int);
int main()
{
const int bs = 512;
const int nb = 32;
const int nvals = bs * nb * NITER;
data *d_; cudaMalloc((void **)&d_, sizeof(data) * size_t(nvals));
aligned_data *ad_; cudaMalloc((void **)&ad_, sizeof(aligned_data) * size_t(nvals));
for(int i=0; i<10; ++i) {
kernel<data,NITER><<<nb, bs>>>(d_, (int *)0, 0);
kernel<aligned_data,NITER><<<nb, bs>>>(ad_, (int *)0, 0);
cudaDeviceSynchronize();
}
cudaDeviceReset();
return 0;
}
我看到对齐的结构版本在计算5.2功能设备上提供了更高的性能:
Time(%) Time Calls Avg Min Max Name
52.71% 2.3995ms 10 239.95us 238.10us 241.79us void kernel<data, int=128>(data*, int*, int)
47.29% 2.1529ms 10 215.29us 214.91us 215.51us void kernel<aligned_data, int=128>(aligned_data*, int*, int)
在这种情况下,我认为大约10%的改进降低到发布的负载指令数量较少。在未对齐的情况下,编译器发出三个32位加载来获取结构,而在对齐的情况下,编译器发出一个128位加载来获取结构。指令的减少似乎抵消了25%浪费的净内存带宽。在具有不同内存指令吞吐量与带宽比的其他硬件上,结果可能会有所不同。