我有一个简单的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();
}
答案 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
这些索引是相邻的,它不是分散的加载或存储,地址将很好地合并,这代表了“有效”使用全局内存。它的执行速度比第一种情况要快。