一个CUDA块维度是否比另一个快?

一个CUDA块维度是否比另一个快?,cuda,Cuda,我有一个简单的CUDA代码,它将NxNmatrixa的值分配给matrixB。在一种情况下,我声明块大小block(1,32),并让每个线程循环到第一个矩阵维度中的条目上。在第二种情况下, 我声明块大小块(32,1),并在 第二矩阵维 在我下面的代码中,为什么在跨步1内存上循环的线程比在跨步N内存上循环的线程要慢得多,这有什么明显的原因吗?我本以为是另一种方式(如果有任何区别的话) 我是否遗漏了一些非常明显的东西(可能是一个bug) 完整的代码如下 #include <stdio.h>

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

在我下面的代码中,为什么在跨步1内存上循环的线程比在跨步N内存上循环的线程要慢得多,这有什么明显的原因吗?我本以为是另一种方式(如果有任何区别的话)

我是否遗漏了一些非常明显的东西(可能是一个bug)

完整的代码如下

#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();
}
#包括
#包括
__全局无效地址x(int m,int n,int*A,int*B)
{    
int idx,ix;
int iy=线程IDX.y+块IDX.y*块DIM.y;
if(iym=1让我们比较每种情况下每个线程生成的全局内存索引

addmat_x:
块尺寸为(1,32)。这意味着
x
中有1个线程宽,32个线程“长”在
y
中,每个线程的
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;
m = 1 << 14;  
...
int ix = threadIdx.x + blockIdx.x*blockDim.x;
idx  = iy*m + ix; 
对于第一次循环迭代,
ix
为0。每个线程生成的idx值为:

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

m = 1 << 14;  
...
int iy = threadIdx.y + blockIdx.y*blockDim.y;
idx  = iy*m + ix;
m = 1 << 14;  
...
int ix = threadIdx.x + blockIdx.x*blockDim.x;
idx  = iy*m + ix; 
对于第一次循环迭代,
iy
为0,因此我们只有:

idx  = threadIdx.x;
这将在扭曲上生成以下索引图案:

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

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

让我们比较每种情况下每个线程生成的全局内存索引

addmat_x:
块尺寸为(1,32)。这意味着
x
中有1个线程宽,32个线程“长”在
y
中,每个线程的
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;
m = 1 << 14;  
...
int ix = threadIdx.x + blockIdx.x*blockDim.x;
idx  = iy*m + ix; 
对于第一次循环迭代,
ix
为0。每个线程生成的idx值为:

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

m = 1 << 14;  
...
int iy = threadIdx.y + blockIdx.y*blockDim.y;
idx  = iy*m + ix;
m = 1 << 14;  
...
int ix = threadIdx.x + blockIdx.x*blockDim.x;
idx  = iy*m + ix; 
对于第一次循环迭代,
iy
为0,因此我们只有:

idx  = threadIdx.x;
这将在扭曲上生成以下索引图案:

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

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

32个线程在x ie中组合在一起。(32,1)将在加载和存储时合并。这是对GPU内存子系统更有效的使用。@Robert Crovella,谢谢。换句话说,线程分组比全局内存访问的布局更重要?它们是相关的。为了进行有效的全局加载或存储,您希望扭曲中的相邻线程读取相邻位置in内存,粗略地说。这是一个非常基本的GPU编程概念,涵盖了许多问题和教程。在“好的”在这种情况下,扭曲读取的行为类似于幻灯片45所示。在坏的情况下,扭曲读取的行为类似于幻灯片53所示。@Robert Crovella幻灯片很棒。在启动扭曲时,如何知道需要哪些全局内存地址?第二个问题是,为什么维度f 1d ThreadBlock很重要,如果SM在warp启动时将它们都视为1d数组,那么如何知道需要哪些全局内存地址呢?
在warp启动时,您不知道这一点。您必须检查代码。您可以查看全局内存读取操作,并计算eac所需的索引只需查看您的代码,扭曲中的h线程就会生成。
如果SM将1d线程块全部视为1d数组,那么为什么1d线程块的尺寸很重要?
这不重要。重要的是线程与代码创建的索引之间的关系。研究每种情况下代码生成的索引。32 threa在x ie(32,1)中组合在一起的ds将在加载和存储时合并。这是GPU内存子系统的更有效使用