CUDA动态数组索引问题:矩阵内部元素求和异常的解决咨询
CUDA动态二维数组索引问题及解决方法
问题描述
学习CUDA时,处理已传输到设备的动态大小数组索引遇到困难。测试案例是仅对两个矩阵的内部元素求和,静态数组结果符合预期,但实际数据量太大无法用栈存储,必须用动态数组。静态数组能正常工作是因为内存紧密连续,而动态数组是指针数组,每行指向内存中不同位置,导致GPU上索引错误,结果不符合预期。网上的示例大多用静态数组,希望得到解决建议,考虑过在主机上扁平化动态矩阵为一维double*后拷贝到设备,但不确定能否保留原索引方案。
现象对比
动态数组运行结果
2.000000 2.000000 2.000000 0.000000 0.000000 2.000000 2.000000 0.000000 0.000000 2.000000 2.000000 2.000000 0.000000 2.000000 2.000000 2.000000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
静态数组运行结果
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 2.000000 2.000000 2.000000 2.000000 0.000000 0.000000 2.000000 2.000000 2.000000 2.000000 0.000000 0.000000 2.000000 2.000000 2.000000 2.000000 0.000000 0.000000 2.000000 2.000000 2.000000 2.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
最小可复现示例(MWE)
#include <stdio.h> #include <stdlib.h> #define THREADS_PER_BLOCK 2 __global__ void matrix_add(double *C, double *A, double *B, int Nx, int Ny); double **create_matrix(int row, int col); int main(void) { int Nx = 6; int Ny = 6; // Create dynamically sized arrays on host double **A = create_matrix(Nx, Ny); double **B = create_matrix(Nx, Ny); double **C = create_matrix(Nx, Ny); // Fill arrays for (int i = 0; i < Nx; i++) { for (int j = 0; j < Ny; j++) { A[i][j] = 1.0; B[i][j] = 1.0; C[i][j] = 0.0; } } // Next create arrays for device double *d_A, *d_B, *d_C; // Allocate memory on GPU int size = Nx * Ny * sizeof(double); cudaMalloc((void **)&d_A, size); cudaMalloc((void **)&d_B, size); cudaMalloc((void **)&d_C, size); // Copy host arrays over to device arrays. cudaMemcpy(d_A, A, size, cudaMemcpyHostToDevice); cudaMemcpy(d_B, B, size, cudaMemcpyHostToDevice); cudaMemcpy(d_C, C, size, cudaMemcpyHostToDevice); dim3 dimBlock(THREADS_PER_BLOCK, THREADS_PER_BLOCK); int blockdimX = (int)ceil(Nx / dimBlock.x); int blockdimY = (int)ceil(Ny / dimBlock.y); dim3 dimGrid(blockdimX, blockdimY); matrix_add<<<dimGrid, dimBlock>>>(d_C, d_A, d_B, Nx, Ny); // Copy back to CPU cudaMemcpy(C, d_C, size, cudaMemcpyDeviceToHost); // inspect result for (int i = 0; i < Nx; i++) { for (int j = 0; j < Ny; j++) { printf("%f ", C[i][j]); } printf("\n"); } return EXIT_SUCCESS; } __global__ void matrix_add(double *C, double *A, double *B, int Nx, int Ny) { int col = threadIdx.x + blockIdx.x * blockDim.x; int row = threadIdx.y + blockIdx.y * blockDim.y; int index = col + row * Nx; if (row != 0 && col != 0 && row < (Ny - 1) && col < (Nx - 1)) { C[index] = A[index] + B[index]; } } double **create_matrix(int row, int col) { double **mat; mat = (double **)calloc(row, sizeof(double *)); for (int i = 0; i < row; i++) { mat[i] = (double *)calloc(col, sizeof(double)); } return mat; }
问题根源与解决方案
问题根源
当前代码的核心错误:主机上用create_matrix创建的是指针数组(double**),本质是一块存储指针的内存,每个指针指向单独分配的一行数据。而cudaMemcpy(d_A, A, size, ...)直接拷贝的是这些指针的地址,GPU无法访问主机内存的指针,导致设备端的d_A中存储的是无效地址,索引计算自然出错。
解决方案:扁平化数组(推荐)
你考虑的主机端扁平化动态矩阵为一维连续数组是正确方案,而且完全可以保留原有的索引计算方式index = col + row * Nx,因为扁平化后的内存布局和静态二维数组完全一致(紧密连续)。
修改步骤:
- 在主机端创建一维连续数组,用于存储矩阵数据
- 将原二维指针数组的数据拷贝到一维数组中
- 拷贝一维数组到GPU,设备端的核函数无需修改(原索引逻辑完全适用)
- 计算完成后,将设备端一维数组拷贝回主机,再转回二维指针数组(或直接用一维数组处理)
修改后的核心代码示例:
int main(void) { int Nx = 6; int Ny = 6; // 原二维指针数组保持不变,用于初始化和结果展示 double **A = create_matrix(Nx, Ny); double **B = create_matrix(Nx, Ny); double **C = create_matrix(Nx, Ny); for (int i = 0; i < Nx; i++) { for (int j = 0; j < Ny; j++) { A[i][j] = 1.0; B[i][j] = 1.0; C[i][j] = 0.0; } } // 创建主机端一维扁平化数组 double *h_A_flat = (double*)malloc(Nx*Ny*sizeof(double)); double *h_B_flat = (double*)malloc(Nx*Ny*sizeof(double)); double *h_C_flat = (double*)malloc(Nx*Ny*sizeof(double)); // 拷贝二维数据到一维数组 for(int i=0; i<Ny; i++){ memcpy(h_A_flat + i*Nx, A[i], Nx*sizeof(double)); memcpy(h_B_flat + i*Nx, B[i], Nx*sizeof(double)); memcpy(h_C_flat + i*Nx, C[i], Nx*sizeof(double)); } // 设备端内存分配与拷贝 double *d_A, *d_B, *d_C; int size = Nx * Ny * sizeof(double); cudaMalloc(&d_A, size); cudaMalloc(&d_B, size); cudaMalloc(&d_C, size); // 拷贝一维数组到GPU cudaMemcpy(d_A, h_A_flat, size, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B_flat, size, cudaMemcpyHostToDevice); cudaMemcpy(d_C, h_C_flat, size, cudaMemcpyHostToDevice); // 核函数调用不变 dim3 dimBlock(THREADS_PER_BLOCK, THREADS_PER_BLOCK); int blockdimX = (int)ceil((double)Nx / dimBlock.x); int blockdimY = (int)ceil((double)Ny / dimBlock.y); dim3 dimGrid(blockdimX, blockdimY); matrix_add<<<dimGrid, dimBlock>>>(d_C, d_A, d_B, Nx, Ny); // 拷贝结果回主机一维数组 cudaMemcpy(h_C_flat, d_C, size, cudaMemcpyDeviceToHost); // 将一维数据转回二维指针数组用于展示 for(int i=0; i<Ny; i++){ memcpy(C[i], h_C_flat + i*Nx, Nx*sizeof(double)); } // 结果输出与内存释放 for (int i = 0; i < Nx; i++) { for (int j = 0; j < Ny; j++) { printf("%f ", C[i][j]); } printf("\n"); } // 释放内存 for(int i=0; i<Nx; i++){ free(A[i]); free(B[i]); free(C[i]); } free(A); free(B); free(C); free(h_A_flat); free(h_B_flat); free(h_C_flat); cudaFree(d_A); cudaFree(d_B); cudaFree(d_C); return EXIT_SUCCESS; }
额外说明
- 扁平化数组是CUDA中处理二维/多维数组的标准方式,内存连续能最大化GPU的内存访问效率
- 如果你坚持要在GPU上使用二维指针数组(不推荐),需要先分配设备端的指针数组,再逐个分配每行的内存并拷贝数据,这种方式会增加内存管理复杂度,且访问效率低于连续内存
内容的提问来源于stack exchange,提问作者Nukesub
相关产品推荐
相关产品推荐

