一个CUDA块尺寸比另一个更快吗?

时间:2018-04-13 23:05:38

标签: cuda

我有一个简单的CUDA代码,可以将NxN矩阵A的值分配给矩阵B。在一种情况下,我声明块大小block(1,32)并让每个线程循环遍历第一个矩阵维度中的条目。在第二种情况下, 我声明块大小block(32,1)并让每个线程循环遍历条目 第二矩阵维。

是否有一些非常明显的原因,在我的代码中,在stride 1内存上循环的线程明显慢于循环跨越N内存的线程?我原本以为它是另一种方式(如果有任何差异)。

我错过了一些非常明显的东西(也许是一个错误)?

完整的代码如下。

#include <stdio.h>
#include <sys/time.h>

__global__ void addmat_x(int m, int n, int* A, int *B) 
{    
    int idx, ix;
    int iy = threadIdx.y + blockIdx.y*blockDim.y;
    if (iy < n) 
        for(ix = 0; ix < m; ix++) {
            idx  = iy*m + ix;    /* iy*m is constant */
            B[idx]   = A[idx];
        }
}

__global__ void addmat_y(int m, int n, int* A, int *B) 
{    
    int ix = threadIdx.x + blockIdx.x*blockDim.x;
    int idx, iy;
    if (ix < m)
        for(iy = 0; iy < n; iy++) {
            idx  = iy*m + ix; 
            B[idx]   = A[idx];        
        }
}

double cpuSecond()
{
    struct timeval tp;
    gettimeofday(&tp,NULL);
    return (double) tp.tv_sec + (double)tp.tv_usec*1e-6;
}

int main(int argc, char** argv) 
{
    int *A, *B;
    int *dev_A, *dev_B;
    size_t m, n, nbytes;
    double etime, start;

    m = 1 << 14;  
    n = 1 << 14;  
    nbytes = m*n*sizeof(int);

    A = (int*) malloc(nbytes);
    B = (int*) malloc(nbytes);

    memset(A,0,nbytes);

    cudaMalloc((void**) &dev_A, nbytes);
    cudaMalloc((void**) &dev_B, nbytes);
    cudaMemcpy(dev_A, A, nbytes, cudaMemcpyHostToDevice);

#if 1
    /* One thread per row */
    dim3 block(1,32);  
    dim3 grid(1,(n+block.y-1)/block.y);
    start = cpuSecond();
    addmat_x<<<grid,block>>>(m,n,dev_A, dev_B);
#else
    /* One thread per column */
    dim3 block(32,1);  
    dim3 grid((m+block.x-1)/block.x,1);
    start = cpuSecond();
    addmat_y<<<grid,block>>>(m,n,dev_A, dev_B);
#endif
    cudaDeviceSynchronize();
    etime = cpuSecond() - start;
    printf("GPU Kernel %10.3g (s)\n",etime);

    cudaFree(dev_A);
    cudaFree(dev_B);
    free(A);
    free(B);

    cudaDeviceReset();
}

1 个答案:

答案 0 :(得分:1)

让我们比较每个线程生成的全局内存索引。

addmat_x:

您的块尺寸为(1,32)。这意味着x中的1个线程宽度,y中的32个线程“长”。每个线程的threadId.x值将为0.当您在warp中从一个线程移动到另一个线程时,warp中线程的threadIdx.y值将在0到31之间。有了它,让我们检查一下你内核中idx的创建:

m = 1 << 14;  
...
int iy = threadIdx.y + blockIdx.y*blockDim.y;
idx  = iy*m + ix;

让我们选择第一个块,blockIdx.y为0.然后:

idx  = threadIdx.y*(1<<14) + ix;

对于第一次循环迭代,ix为0.每个线程生成的idx值为:

threadIdx.y:   | idx:
   0              0
   1                (1<<14)
   2              2*(1<<14)
  ...
   31            31*(1<<14)

对于给定的循环迭代,从一个线程到下一个线程的加载或存储索引的距离将是1 <&lt; 14。即不相邻。散射。

addmat_y:

您的块尺寸为(32,1)。这意味着x中有32个线程,y中有1个线程“长”。每个线程的threadIdx.y值将为0.当您从一个线程移动到另一个线程时,warp中线程的threadIdx.x值将在0到31之间。现在让我们检查一下你内核中idx的创建:

m = 1 << 14;  
...
int ix = threadIdx.x + blockIdx.x*blockDim.x;
idx  = iy*m + ix; 

让我们选择第一个块,blockIdx.x为0.然后:

idx  = iy*m + threadIdx.x; 

对于第一次循环迭代,iy为0,所以我们只需:

idx  = threadIdx.x;

这会在warp中生成以下索引模式:

threadIdx.x:   | idx:
   0              0
   1              1
   2              2
  ...
   31            31

这些索引是相邻的,它不是分散的加载或存储,地址将很好地合并,这代表了“有效”使用全局内存。它的执行速度比第一种情况要快。