GPU创建矩阵并CPU打印的CUDA代码错误排查求助
咱们来一步步拆解这段代码里的问题,主要集中在设备内存分配、线程配置和数据拷贝这几个核心环节:
1. 设备二维数组的分配逻辑错误
你在主机端的a是典型的指针数组(每个元素指向一行内存),但设备端的d_a是存放在设备内存中的指针数组,主机代码不能直接访问d_a[i](这属于设备内存空间,主机只能通过CUDA API操作)。看你这段循环:
for (int i =0; i < N; i++){ cudaMalloc((void**)&d_a,N*sizeof(int)); }
这里每次都是把新分配的设备内存地址直接赋值给d_a本身,完全没有给d_a这个指针数组的元素赋值——相当于你分配了N次设备内存,但最后只保留了最后一次的地址,还丢了之前的,同时设备端的指针数组d_a里的元素全是无效值,kernel访问时必然出错。
正确的做法是:
- 先在主机上创建一个临时指针数组
h_d_a,用来存储每一行设备内存的地址 - 循环为每一行分配设备内存,把地址存在
h_d_a[i] - 再把
h_d_a整个拷贝到设备端的d_a中,让设备端的指针数组能正确指向每一行的内存
2. 线程配置与kernel逻辑不匹配
你的kernel里用了threadIdx.x和threadIdx.y来定位矩阵的(i,j)元素,但启动kernel时用的是<<<1, N>>>——这意味着线程块里只有N个一维线程,所有线程的threadIdx.y都是0,当j>0时就会触发越界访问,直接导致CUDA错误。
需要改成二维线程配置:<<<1, dim3(N, N)>>>,这样每个线程对应矩阵的一个(i,j)位置,和kernel里的索引逻辑匹配。另外最好在kernel里加个边界判断,避免线程索引超出矩阵范围(比如当N不是线程块维度的整数倍时,但这里我们刚好是N×N,加了更稳妥)。
3. 数据拷贝逻辑完全错误
主机的a是指针数组,每个元素指向独立的主机内存块;设备的d_a也是指针数组,每个元素指向独立的设备内存块。你现在直接用:
cudaMemcpy(a, d_a, (N*N)*sizeof(int),cudaMemcpyDeviceToHost);
这会把设备上的指针数组本身(N个指针地址,共N×8字节)拷贝到主机的a数组里,而不是矩阵的实际数据——之后你打印a[i][j]时,访问的是无效地址,必然崩溃或者输出垃圾值。
正确的做法是逐行拷贝:循环遍历每一行,把设备上对应行的内存拷贝到主机对应行的内存中。
4. 内存泄漏问题
你只调用了cudaFree(d_a),但完全没有释放循环中分配的每一行设备内存,会造成设备内存泄漏。需要先释放每一行的设备内存,再释放指针数组d_a本身(注意要通过主机端的h_d_a来获取每行的设备地址)。
修正后的完整代码
#include <stdio.h> #include <stdlib.h> __global__ void create(int **d_a){ int i = threadIdx.x; int j = threadIdx.y; // 边界判断,避免线程索引越界 if (i < N && j < N) { d_a[i][j] = 1; } } void errorCheck(){ cudaError_t error = cudaGetLastError(); if(error != cudaSuccess){ printf("CUDA error: %s\n", cudaGetErrorString(error)); exit(-1); } } #define N 5 int main(){ int **d_a, **a; int **h_d_a; // 主机端临时存储设备每行的内存地址 // 分配主机端二维数组 a = (int**)malloc(N * sizeof(int*)); for (int i = 0; i < N; i++){ a[i] = (int*)malloc(N * sizeof(int)); } // 分配设备端的指针数组(存储每行的设备地址) cudaMalloc((void***)&d_a, N * sizeof(int*)); // 主机端临时数组,用来暂存每行的设备内存地址 h_d_a = (int**)malloc(N * sizeof(int*)); for (int i = 0; i < N; i++){ cudaMalloc((void**)&h_d_a[i], N * sizeof(int)); } // 将每行的设备地址拷贝到设备端的指针数组d_a中 cudaMemcpy(d_a, h_d_a, N * sizeof(int*), cudaMemcpyHostToDevice); errorCheck(); // 启动二维线程块,每个线程对应矩阵的一个元素 create <<<1, dim3(N, N)>>>(d_a); errorCheck(); // 逐行拷贝设备数据到主机内存 for (int i = 0; i < N; i++){ cudaMemcpy(a[i], h_d_a[i], N * sizeof(int), cudaMemcpyDeviceToHost); } errorCheck(); // 打印矩阵 for (int i = 0; i < N; i++ ){ for (int j = 0; j < N; j++ ){ printf("%d ", a[i][j]); } printf("\n"); } // 释放设备内存:先释放每行的内存,再释放指针数组 for (int i = 0; i < N; i++){ cudaFree(h_d_a[i]); } cudaFree(d_a); // 释放主机内存 free(h_d_a); for (int i = 0; i < N; i++){ free(a[i]); } free(a); return 0; }
额外优化建议
其实在CUDA中,使用连续的二维内存(比如把矩阵 flatten 成一维数组,或者用cudaMallocPitch分配对齐的连续内存)会比指针数组更高效,因为指针数组的内存是分散的,会增加内存访问的延迟。比如可以把kernel改成用一维索引:
__global__ void create(int *d_a){ int idx = threadIdx.x * N + threadIdx.y; if (idx < N*N) { d_a[idx] = 1; } }
这样分配内存和拷贝都会更简单,性能也更好。
内容的提问来源于stack exchange,提问作者killgore_bot

