1. 函数作用

cudaMemcpy 是CUDA中用于 内存数据复制 的核心函数,主要功能包括:

  • 主机(CPU)↔ 设备(GPU):在CPU内存和GPU显存之间传输数据。
  • 设备(GPU)↔ 设备(GPU):在GPU显存的不同区域之间复制数据。

类比理解:就像快递员在不同仓库(CPU内存和GPU显存)之间搬运货物(数据)。


2. 函数原型

cudaError_t cudaMemcpy(
    void* dst,          // 目标地址(指向复制目的地的指针)
    const void* src,    // 源地址(指向数据源的指针)
    size_t count,       // 复制的字节数
    cudaMemcpyKind kind // 传输方向标识
);

3. 参数详解

(1)dst
  • 类型void*
  • 作用:目标内存地址(数据复制的目的地)
  • 注意事项
    • 如果方向是 Host→Devicedst 必须是设备端指针(通过 cudaMalloc 分配)。
    • 如果方向是 Device→Hostdst 必须是主机端指针(如 malloc 分配)。
(2)src
  • 类型const void*
  • 作用:源内存地址(数据复制的来源)
  • 注意事项
    • 如果方向是 Host→Devicesrc 必须是主机端指针。
    • 如果方向是 Device→Hostsrc 必须是设备端指针。
(3)count
  • 类型size_t

  • 作用:需要复制的字节数

  • 计算示例

    int N = 1024;
    size_t size = N * sizeof(float);  // 计算总字节数
    
(4)kind
  • 类型cudaMemcpyKind(枚举类型)
  • 作用:指定数据传输方向
传输方向标识说明
cudaMemcpyHostToHost主机内存 → 主机内存(通常不使用)
cudaMemcpyHostToDevice主机内存 → 设备显存
cudaMemcpyDeviceToHost设备显存 → 主机内存
cudaMemcpyDeviceToDevice设备显存 → 设备显存
cudaMemcpyDefault自动判断方向(需统一内存支持)

4. 使用示例

场景1:主机→设备(上传数据)
int *h_data = (int*)malloc(1024 * sizeof(int));  // 主机内存
int *d_data;
cudaMalloc(&d_data, 1024 * sizeof(int));        // 设备内存

// 初始化主机数据
for(int i=0; i<1024; i++) h_data[i] = i;

// 拷贝到设备
cudaMemcpy(d_data, h_data, 1024*sizeof(int), cudaMemcpyHostToDevice);
场景2:设备→主机(取回结果)
int *h_result = (int*)malloc(1024 * sizeof(int));

// 拷贝回主机
cudaMemcpy(h_result, d_data, 1024*sizeof(int), cudaMemcpyDeviceToHost);

// 打印结果
for(int i=0; i<5; i++) printf("%d ", h_result[i]);  // 输出:0 1 2 3 4

5. 常见错误及处理

错误1:指针类型错误
int *h_data = (int*)malloc(1024 * sizeof(int));
int *d_data;
cudaMalloc(&d_data, 1024 * sizeof(int));

// 错误示例:方向标识错误
cudaMemcpy(d_data, h_data, 1024*sizeof(int), cudaMemcpyDeviceToHost); // ❌
  • 错误原因:方向标识与指针类型不匹配。
  • 解决方法:检查传输方向和指针类型。
错误2:内存未分配
int *h_data;  // 未分配内存
int *d_data;
cudaMalloc(&d_data, 1024 * sizeof(int));

cudaMemcpy(d_data, h_data, 1024*sizeof(int), cudaMemcpyHostToDevice); // ❌
  • 错误原因:主机指针 h_data 未分配内存。
  • 解决方法:使用 mallocnew 分配内存。
错误3:字节数错误
int N = 1024;
int *h_data = (int*)malloc(N * sizeof(int));
int *d_data;
cudaMalloc(&d_data, N * sizeof(int));

// 错误示例:少复制了数据
cudaMemcpy(d_data, h_data, 512*sizeof(int), cudaMemcpyHostToDevice); // ❌
  • 错误原因:复制的字节数小于实际数据大小。
  • 解决方法:准确计算 count 参数。

6. 最佳实践

  1. 始终检查返回值

    cudaError_t err = cudaMemcpy(...);
    if(err != cudaSuccess) {
        printf("Error: %s\n", cudaGetErrorString(err));
        exit(1);
    }
    
  2. 使用 CHECK 宏简化代码

    #define CHECK(call) {                              \
        cudaError_t err = call;                        \
        if(err != cudaSuccess) {                       \
            printf("Error: %s:%d\n", __FILE__, __LINE__); \
            printf("Code:%d, Reason:%s\n", err, cudaGetErrorString(err)); \
            exit(1);                                   \
        }                                              \
    }
    
    CHECK(cudaMemcpy(d_data, h_data, size, cudaMemcpyHostToDevice));
    
  3. 优先使用 cudaMemcpyDefault(需统一内存)

    // 自动判断方向(需配置统一内存)
    cudaMemcpy(dst, src, size, cudaMemcpyDefault);
    

7. 性能提示

  • 减少传输次数:合并多次小数据传输为单次大数据传输。
  • 异步传输:使用 cudaMemcpyAsync 实现数据传输与计算重叠。
  • 固定内存(Pinned Memory):使用 cudaMallocHost 分配主机内存可加速传输。

总结

cudaMemcpy 是连接主机与设备数据的桥梁,正确使用时需注意:

  1. 指针类型:严格区分主机和设备指针。
  2. 传输方向:明确标识 HostToDeviceDeviceToHost
  3. 错误检查:始终验证返回值,避免静默失败。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐