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