在warp中对齐12字节结构 - cuda

时间:2016-11-15 09:17:13

标签: arrays memory struct cuda

假设我们有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字节。

因此,这意味着第一个示例更高效,或者第二个示例仍然更有效,尽管它获取更多内存。

1 个答案:

答案 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%浪费的净内存带宽。在具有不同内存指令吞吐量与带宽比的其他硬件上,结果可能会有所不同。