使用cudaHostAlloc后cudaMemcpy性能未提升,求问题排查
我有一个基于cuBLAS执行BLAS gemm()子程序的程序,代码如下:
size_t m = 10000; size_t k = 4000; size_t n = 6000; // h_A 是 m×k 矩阵,h_B 是 k×n 矩阵,h_C 是 m×n 矩阵 double *h_C; cudaHostAlloc((void **)(&h_C), m * n * sizeof(double), cudaHostAllocDefault); uint64_t t0 = get_timestamp_in_microsec(); CUBLAS_CHECK(cublasCreate(&cublasH)); uint64_t t1 = get_timestamp_in_microsec(); CUDA_CHECK(cudaMalloc((void **)(&d_A), sizeof(double) * m * k)); CUDA_CHECK(cudaMalloc((void **)(&d_B), sizeof(double) * k * n)); CUDA_CHECK(cudaMalloc((void **)(&d_C), sizeof(double) * m * n)); uint64_t t2 = get_timestamp_in_microsec(); CUDA_CHECK(cudaMemcpy(d_A, h_A.data(), sizeof(double) * m * k, cudaMemcpyHostToDevice)); CUDA_CHECK(cudaMemcpy(d_B, h_B.data(), sizeof(double) * k * n, cudaMemcpyHostToDevice)); uint64_t t3 = get_timestamp_in_microsec(); CUBLAS_CHECK(cublasDgemm(cublasH, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, d_A, lda, d_B, ldb, &beta, d_C, ldc)); uint64_t t4 = get_timestamp_in_microsec(); CUDA_CHECK(cudaMemcpy(h_C, d_C, sizeof(double) * m * n, cudaMemcpyDeviceToHost)); uint64_t t5 = get_timestamp_in_microsec();
程序整体性能尚可,但最后将d_C拷贝回h_C的cudaMemcpy()耗时近3秒,性能数据如下:
t1-t0: 12.929ms t2-t1: 0.949ms t3-t2: 53.256ms t4-t3: 0.315ms t5-t4: 2653.57ms
我查阅资料后,将malloc()替换为cudaHostAlloc(),但上述性能数据无任何变化。请问我的代码是否存在问题?当前性能是否已达最优?
代码问题排查
时间测量的核心误差
cuBLAS的cublasDgemm是异步执行的,你在调用后立即记录t4,此时gemm计算可能还未完成。后续的cudaMemcpy(DeviceToHost)会隐式同步等待GPU任务结束,所以t5-t4的时间实际包含了gemm剩余执行时间和数据拷贝时间,并非单纯的拷贝耗时——这是你当前性能数据异常的主要原因。cudaHostAlloc的有效性
你用cudaHostAllocDefault分配了h_C,但该标志在部分系统上仅分配可分页的 pinned 内存,若设备不支持相关特性,收益有限。另外,需确认h_A、h_B的分配方式是否也做了优化,但这不是当前拷贝耗时的直接诱因。
性能优化建议
修正时间测量逻辑
在cublasDgemm后添加显式同步,确保GPU计算完成后再记录时间,才能准确区分计算和拷贝的耗时:CUBLAS_CHECK(cublasDgemm(cublasH, CUBLAS_OP_N, CUBLAS_OP_N, m, n, k, &alpha, d_A, lda, d_B, ldb, &beta, d_C, ldc)); CUDA_CHECK(cudaDeviceSynchronize()); // 等待gemm执行完毕 uint64_t t4 = get_timestamp_in_microsec();优化数据拷贝效率
- 换用
cudaHostAllocWriteCombined标志分配h_C:该标志会让CPU不缓存这块内存,避免缓存污染,提升从设备到主机的拷贝速度(适合仅读取的场景)。 - 检查PCIe链路状态:你的
d_C数据量为10000*6000*8字节=480MB,PCIe 3.0 x16理论带宽约16GB/s,理想拷贝耗时约30ms。若修正测量后仍耗时过高,需排查系统PCIe链路宽度(是否处于x1等低带宽模式)、是否有其他进程占用GPU/PCIe资源。 - 尝试异步拷贝:如果后续有其他GPU计算任务,可使用
cudaMemcpyAsync配合CUDA流,让数据拷贝与后续计算重叠,隐藏拷贝耗时。
- 换用
确认矩阵布局正确性
cuBLAS默认要求列优先存储,检查lda、ldb、ldc参数是否符合列优先的布局要求(通常设为矩阵的行数)。布局错误可能导致gemm执行异常,间接影响整体耗时。
当前性能是否最优?
显然不是。当前的时间测量误差导致你无法评估真实的拷贝性能,修正后正常拷贝耗时应在几十毫秒级别。若修正后仍存在瓶颈,需排查系统层面的问题(如PCIe配置、驱动版本、设备功耗模式等)。
内容的提问来源于stack exchange,提问作者D.J. Elkind

