我正在努力优化cuda计划。所以我首先开始优化矩阵乘法程序。我用于并行化的线程化方案是Blocksize(1,1),Gridsize(N,N)。我正在使用表面存储器进行内存优化(因为这种线程方案不可能使用共享内存)。当我比较优化之前和之后的时间时,我发现使用表面存储器后执行需要两倍的时间(我尝试过不同的线程方案,但问题仍然存在)。从我读过的东西到现在,全局内存比表面内存慢。因此,使用表面存储器应该花费更少的时间。下面我给出了使用表面存储器的矩阵乘法程序。有人能告诉我这是什么问题吗?
#include < stdio.h >
#include < cuda.h >
//#define N 3
surface < void, 2 > a_surf;
surface < void, 2 > b_surf;
surface < void, 2 > c_surf;
void CUDA_SAFE_CALL(cudaError_t call, int line) {
switch (call) {
case cudaSuccess:
break;
default:
printf("ERROR at line :%i.%d' ' %s\n",
line, call, cudaGetErrorString(call));
exit(-1);
break;
}
}
__global__ void mul(int N) {
int a, b, c, temp;
int i;
unsigned int x = blockIdx.x * blockDim.x + (threadIdx.x);
unsigned int y = blockIdx.y * blockDim.y + (threadIdx.y);
if (x < N && y < N) {
temp = 0;
for (i = 0; i < N; i++) {
surf2Dread( & a, a_surf, (x) * 4, i);
surf2Dread( & b, b_surf, (i) * 4, y);
temp += a * b;
}
c = temp;
// Write to output surface
surf2Dwrite(c, c_surf, x * 4, y);
}
}
int main() {
int N = 100;
int a[N][N], b[N][N], c[N][N];
int i, j;
int temp;
clock_t t1, t2;
cudaArray * da, * db, * dc;
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc < int > ();
dim3 dimBlock(1, 1);
dim3 dimGrid(N, N);
temp = 0;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
a[i][j] = ++temp;
temp = 0;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
b[i][j] = ++temp;
CUDA_SAFE_CALL(cudaMallocArray( & da, & channelDesc, N, N, cudaArraySurfaceLoadStore), __LINE__);
CUDA_SAFE_CALL(cudaMallocArray( & db, & channelDesc, N, N, cudaArraySurfaceLoadStore), __LINE__);
CUDA_SAFE_CALL(cudaMallocArray( & dc, & channelDesc, N, N, cudaArraySurfaceLoadStore), __LINE__);
int s = N * N * sizeof(int);
CUDA_SAFE_CALL(cudaMemcpyToArray(da, 0, 0, a, s, cudaMemcpyHostToDevice), __LINE__);
CUDA_SAFE_CALL(cudaMemcpyToArray(db, 0, 0, b, s, cudaMemcpyHostToDevice), __LINE__);
CUDA_SAFE_CALL(cudaBindSurfaceToArray(a_surf, da), __LINE__);
CUDA_SAFE_CALL(cudaBindSurfaceToArray(b_surf, db), __LINE__);
CUDA_SAFE_CALL(cudaBindSurfaceToArray(c_surf, dc), __LINE__);
t1 = clock();
mul <<<dimGrid, dimBlock>>> (N);
t2 = clock();
CUDA_SAFE_CALL(cudaMemcpyFromArray(c, dc, 0, 0, s, cudaMemcpyDeviceToHost), __LINE__);
double t3 = (double) t2 - (double) t1;
t3 = t3 / CLOCKS_PER_SEC;
printf("\n CUDA time :%lf", t3);
CUDA_SAFE_CALL(cudaFreeArray(da), __LINE__);
CUDA_SAFE_CALL(cudaFreeArray(db), __LINE__);
CUDA_SAFE_CALL(cudaFreeArray(dc), __LINE__);
}
答案 0 :(得分:4)
优化缓存并非易事。所以如此夸张的概括:
在我看来,从我读到的任何内容到现在,全局内存比表面内存慢。因此,使用表面记忆应该花费更少的时间。
是如此宽泛到不正确。它经常 ,但并非总是如此。细节很重要,正确的编程实践也很重要。
表面存储器只不过是具有中间缓存的全局存储器。但全局内存(在当前CUDA版本支持的所有GPU上)已经支持L2(在某些情况下为L1)缓存。
您提出的用于测试/比较的代码有许多问题需要指出:
您的计时方法不正确。这样:
t1 = clock();
mul <<<dimGrid, dimBlock>>> (N);
t2 = clock();
将计算内核启动的持续时间,而不是内核执行的持续时间。所以这几乎不是正确计时的方法。我们可以通过在时序区域中调用cudaDeviceSynchronize();
来解决这个问题,以便在时序关闭之前强制完成内核。
如果您对表现感兴趣,这是一个特别糟糕的结构:
dim3 dimBlock(1, 1);
因为每个GPU warp中每32个线程中有31个将处于非活动状态,所以您将使31/32的GPU性能未使用。这具有广泛的影响。我没有兴趣研究这种场景的性能,你也不应该(因为它不能反映出编写良好的代码的真实性能),除非你对微基准测试(不是比较基准)感兴趣。因此,应修复您的代码以处理至少32个,理想情况下每个块处理256个或更多个线程。
您没有提供“全局记忆”比较案例。所以我会提供一个。
您没有说明许多其他因素对比较基准测试或性能分析很重要,例如您运行的GPU和平台,以及编译命令。
在我看来,问题的规模太小了。矩阵乘以100x100矩阵位于代码的边缘,可以合理地占用GPU,或测试其性能限制。所以我会把问题的规模扩大。
关于问题大小参数,这对于缓存讨论很重要。首先,表面高速缓存往往是空间优化的高速缓存,而普通的L1和L2高速缓存是线性(高速缓存行)优化的。对于非常大的2D问题,表面缓存可能提供比L2更好的行为。但对于非常小的问题,差异将不太明显。其次,表面缓存另外到L1和L2缓存,因此一个好的优化策略是通过L1和L2汇集一些数据,并通过表面汇集其他数据,以最大化可用的缓存行。事实上,由于您的输入矩阵是只读的,因此进一步优化可能是使用纹理而不是表面。但是从相反的角度来看,如果我的问题如此小以至于完全适合L2缓存,则表面缓存不太可能带来显着的改进。原始问题大小包括3个100x100 int
量的矩阵,因此每个大约40K字节,或总共120K字节。此问题大小将适合大多数GPU的L2缓存。通过增加问题的大小(正如我们将要看到的那样 - 总共大约12MB),我们可以严重阻碍全局内存的情况。
这是一个代码和完整工作的示例,已经过修改以解决上述大多数问题。当我在CUDA 7.5 / Fedora 20上的Quadro5000 GPU上运行此代码时,我发现表面情况比全局内存情况快8倍:
$ cat t1129.cu
#include <stdio.h>
#include <iostream>
typedef int mytype;
const int blk_dim=16;
#define my_N 1000
#define A_VAL 1
#define B_VAL 2
surface < void, 2 > a_surf;
surface < void, 2 > b_surf;
surface < void, 2 > c_surf;
void CUDA_SAFE_CALL(cudaError_t call, int line) {
switch (call) {
case cudaSuccess:
break;
default:
printf("ERROR at line :%i.%d' ' %s\n",
line, call, cudaGetErrorString(call));
exit(-1);
break;
}
}
#ifdef USE_GLOBAL
__global__ void mul(const mytype * __restrict__ d_a, const mytype * __restrict__ d_b, mytype * __restrict__ d_c, const int N)
#else
__global__ void mul(const int N)
#endif
{
mytype a, b, c, temp;
int i;
unsigned int x = blockIdx.x * blockDim.x + (threadIdx.x);
unsigned int y = blockIdx.y * blockDim.y + (threadIdx.y);
if (x < N && y < N) {
temp = 0;
for (i = 0; i < N; i++) {
#ifdef USE_GLOBAL
a = d_a[x*N+i];
b = d_b[i*N+y];
#else
surf2Dread( & a, a_surf, (x) * sizeof(mytype), i);
surf2Dread( & b, b_surf, (i) * sizeof(mytype), y);
#endif
temp += a * b;
}
c = temp;
#ifdef USE_GLOBAL
d_c[x*N+y] = c;
#else
// Write to output surface
surf2Dwrite(c, c_surf, x * sizeof(mytype), y);
#endif
}
}
int main() {
const int N = my_N;
mytype *a, *b, *c, *d_a, *d_b, *d_c;
int i, j;
clock_t t1, t2;
cudaArray * da, * db, * dc;
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc < mytype > ();
dim3 dimBlock(blk_dim, blk_dim);
dim3 dimGrid((N+dimBlock.x-1)/dimBlock.x, (N+dimBlock.y-1)/dimBlock.y);
int s = N * N * sizeof(mytype);
a = (mytype *)malloc(s);
b = (mytype *)malloc(s);
c = (mytype *)malloc(s);
CUDA_SAFE_CALL(cudaMalloc(&d_a, s), __LINE__);
CUDA_SAFE_CALL(cudaMalloc(&d_b, s), __LINE__);
CUDA_SAFE_CALL(cudaMalloc(&d_c, s), __LINE__);
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
a[i*N+j] = A_VAL;
for (i = 0; i < N; i++)
for (j = 0; j < N; j++)
b[i*N+j] = B_VAL;
CUDA_SAFE_CALL(cudaMallocArray( & da, & channelDesc, N, N, cudaArraySurfaceLoadStore), __LINE__);
CUDA_SAFE_CALL(cudaMallocArray( & db, & channelDesc, N, N, cudaArraySurfaceLoadStore), __LINE__);
CUDA_SAFE_CALL(cudaMallocArray( & dc, & channelDesc, N, N, cudaArraySurfaceLoadStore), __LINE__);
CUDA_SAFE_CALL(cudaMemcpyToArray(da, 0, 0, a, s, cudaMemcpyHostToDevice), __LINE__);
CUDA_SAFE_CALL(cudaMemcpyToArray(db, 0, 0, b, s, cudaMemcpyHostToDevice), __LINE__);
CUDA_SAFE_CALL(cudaBindSurfaceToArray(a_surf, da), __LINE__);
CUDA_SAFE_CALL(cudaBindSurfaceToArray(b_surf, db), __LINE__);
CUDA_SAFE_CALL(cudaBindSurfaceToArray(c_surf, dc), __LINE__);
#ifdef USE_GLOBAL
CUDA_SAFE_CALL(cudaMemcpy(d_a, a, s, cudaMemcpyHostToDevice), __LINE__);
CUDA_SAFE_CALL(cudaMemcpy(d_b, b, s, cudaMemcpyHostToDevice), __LINE__);
#endif
t1 = clock();
#ifdef USE_GLOBAL
mul <<<dimGrid, dimBlock>>> (d_a, d_b, d_c, N);
#else
mul <<<dimGrid, dimBlock>>> (N);
#endif
cudaDeviceSynchronize();
t2 = clock();
CUDA_SAFE_CALL(cudaMemcpyFromArray(c, dc, 0, 0, s, cudaMemcpyDeviceToHost), __LINE__);
#ifdef USE_GLOBAL
CUDA_SAFE_CALL(cudaMemcpy(c, d_c, s, cudaMemcpyDeviceToHost), __LINE__);
#endif
double t3 = (double) t2 - (double) t1;
t3 = t3 / CLOCKS_PER_SEC;
printf("\n CUDA time :%lf\n", t3);
for (i=0; i < N*N; i++)
if(c[i] != A_VAL*B_VAL*N) {std::cout << "mismatch at: " << i << ", was: " << c[i] << " should be: " << A_VAL*B_VAL*N << std::endl; return 1;}
CUDA_SAFE_CALL(cudaFreeArray(da), __LINE__);
CUDA_SAFE_CALL(cudaFreeArray(db), __LINE__);
CUDA_SAFE_CALL(cudaFreeArray(dc), __LINE__);
std::cout << "Success!" << std::endl;
return 0;
}
[bob@cluster1 misc]$ nvcc -O3 -o t1129 t1129.cu
[bob@cluster1 misc]$ ./t1129
CUDA time :0.028771
Success!
$ nvcc -O3 -DUSE_GLOBAL -o t1129 t1129.cu
$ ./t1129
CUDA time :0.243635
Success!
$
作为最后一点,我们可以谈论许多其他优化,这可能会改变比较的方式。但是如果你真的想做快速矩阵乘法运算,你应该使用CUBLAS。你不应该编写自己的矩阵乘法例程。