cuda编程笔记(24)-- Global Memory之间的拷贝

如果要在核函数内做比较大规模一点的数组拷贝,应该怎么做呢?

最简单的想法自然是每个线程拷贝对应的下标

cpp 复制代码
__global__ void globalCopy(const float* data, float* out,const int N) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < N) {
        // 直接读全局内存
        out[idx] = data[idx];
    }
}

这并没有什么问题,但是是否能够加速呢?

使用共享内存作缓存

令人反直觉的想法是,可以先把源数组的内容放到共享内存,再由共享内存拷贝到目的数组。

cpp 复制代码
//假如一个block只启动一个warp(32线程)
__global__ void SharedMemoryCopy(const float* data, float* out,const int N){
    __shared__ float SharedMemory[32];
    int idx=threadIdx.x+blockIdx.x*blockDim.x;
    if(threadIdx.x < 32 &&idx<N){
        SharedMemory[threadIdx.x]=data[idx];
    }
    __syncthreads();
    if(threadIdx.x < 32 &&idx<N){
        out[idx]=SharedMemory[threadIdx.x];
    }
}

向量化拷贝

如果拷贝的时候相邻的内容会一起拷贝,可以考虑向量化拷贝

cpp 复制代码
__global__ void VectorCopy(float* data, float* out,const int N){
    using Vec=float4;//float4有4个float数据
    const int vec_size=4;
    const int vec_num=N/vec_size;//这里默认N是4的倍数,不然得作尾部处理
    int idx=threadIdx.x+blockIdx.x*blockDim.x;
    if(idx<vec_num){
        Vec *vec_src_ptr=reinterpret_cast<Vec*>(data)+idx;
        Vec *vec_dst_ptr=reinterpret_cast<Vec*>(out)+idx;
        *vec_dst_ptr=*vec_src_ptr;
    }
}

测试

cpp 复制代码
#include <cuda_runtime.h>
#include <iostream>
#include <vector>

__global__ void globalCopy(const float* data, float* out,const int N) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < N) {
        // 直接读全局内存
        out[idx] = data[idx];
    }
}
//假如一个block只启动一个warp(32线程)
__global__ void SharedMemoryCopy(const float* data, float* out,const int N){
    __shared__ float SharedMemory[32];
    int idx=threadIdx.x+blockIdx.x*blockDim.x;
    if(threadIdx.x < 32 &&idx<N){
        SharedMemory[idx]=data[idx];
    }
    __syncthreads();
    if(threadIdx.x < 32 &&idx<N){
        out[idx]=SharedMemory[idx];
    }
}
__global__ void VectorCopy(float* data, float* out,const int N){
    using Vec=float4;
    const int vec_size=4;
    const int vec_num=N/vec_size;
    int idx=threadIdx.x+blockIdx.x*blockDim.x;
    if(idx<vec_num){
        Vec *vec_src_ptr=reinterpret_cast<Vec*>(data)+idx;
        Vec *vec_dst_ptr=reinterpret_cast<Vec*>(out)+idx;
        *vec_dst_ptr=*vec_src_ptr;
    }
}
int main() {
    cudaEvent_t start,end;
    float SharedMemoryCopyTime=0,globalCopyTime=0,VectorCopyTime=0;
    cudaEventCreate(&start);
    cudaEventCreate(&end);

    float host_data[32];
    std::fill_n(host_data,32,1.0f);
    float *d_data,*d_out;
    cudaMalloc(&d_data,32*sizeof(float));
    cudaMalloc(&d_out,32*sizeof(float));
    cudaMemcpy(d_data,host_data,32*sizeof(float),cudaMemcpyHostToDevice);

    cudaEventRecord(start);
    globalCopy<<<1,32>>>(d_data,d_out,32);
    cudaEventRecord(end);
    cudaEventSynchronize(end);
    cudaEventElapsedTime(&globalCopyTime,start,end);

    cudaEventRecord(start);
    SharedMemoryCopy<<<1,32>>>(d_data,d_out,32);
    cudaEventRecord(end);
    cudaEventSynchronize(end);
    cudaEventElapsedTime(&SharedMemoryCopyTime,start,end);

    cudaEventRecord(start);
    VectorCopy<<<1,32>>>(d_data,d_out,32);
    cudaEventRecord(end);
    cudaEventSynchronize(end);
    cudaEventElapsedTime(&VectorCopyTime,start,end);

    std::cout<<"globalCopy的时间是: "<<globalCopyTime<<std::endl;
    std::cout<<"SharedMemoryCopy的时间是: "<<SharedMemoryCopyTime<<std::endl;
    std::cout<<"VectorCopy的时间是: "<<VectorCopyTime<<std::endl;
    cudaFree(d_data);
    cudaFree(d_out);
    cudaEventDestroy(start);
    cudaEventDestroy(end);
}
bash 复制代码
globalCopy的时间是: 138.551
SharedMemoryCopy的时间是: 36.309
VectorCopy的时间是: 36.5588

(单位是ms)

为什么?

全局内存访问(globalCopy)

  • 每次 out[idx] = data[idx]; 都是 全局内存读取 + 写回

  • GPU 全局内存延迟大约 400--600 cycles

  • 即使 L1/L2 缓存能命中,也比共享内存慢很多

  • 所以时间最长:138 ms(实验结果)

使用共享内存(SharedMemoryCopy)

  • 步骤:

    1. SharedMemory[idx] = data[idx]; → 读全局内存到共享内存

    2. out[idx] = SharedMemory[idx]; → 从共享内存写回全局内存

  • 关键点

    • 虽然多了一次写入共享内存,但共享内存访问非常快(几 cycles)

    • 如果多个线程访问同一个 warp 或相邻地址,共享内存是 完全 coalesced 的寄存器级别速度

    • 第一次全局内存读的 latency 被 warp 内隐藏(大量线程可以 overlap)

  • 因此,整体来看,比直接每次全局内存读写快很多

VectorCopy(float4)

  • 将 float 拆成 float4,一次读写 4 个 float

  • 优点:

    • 全局内存访问 coalesced,一次 transaction 传 16 bytes

    • warp 内存访问效率极高

核心原因总结

  1. 全局内存慢 → globalCopy 最慢

  2. 共享内存快 + warp 重叠 → 虽然多了拷贝,但每个线程对全局内存访问少了一次,速度提升

  3. Vectorized load → float4 减少 memory transaction 数量,也接近共享内存速度

💡 简单理解:

GPU 的瓶颈不是"计算",而是 全局内存访问延迟和带宽 。把数据先放共享内存,相当于 一次全局内存读,多次快速访问共享内存,整体就快了。

把向量化和共享内存结合呢?

cpp 复制代码
//假如一个block只启动一个warp(32线程)
__global__ void VectorSharedMemoryCopy(float* data, float* out,const int N){
    using Vec=float4;
    __shared__ Vec SharedMemory[8];
    const int vec_size=4;
    const int vec_num=N/vec_size;
    int idx=threadIdx.x+blockIdx.x*blockDim.x;
    if(threadIdx.x < 8 &&idx<N){
        SharedMemory[threadIdx.x]=*(reinterpret_cast<Vec*>(data)+idx);
    }
    __syncthreads();
    if(threadIdx.x < 8 &&idx<N){
        *(reinterpret_cast<Vec*>(out)+idx)=SharedMemory[threadIdx.x];
    }
}



//启动
VectorSharedMemoryCopy<<<1,32>>>(d_data,d_out,32);
bash 复制代码
VectorSharedMemoryCopy的时间是: 35.1693

没啥明显提升,可能的原因是数据量太小了

相关推荐
ZC跨境爬虫5 小时前
跟着 MDN 学 HTML day_9:(信件语义标记)
前端·css·笔记·ui·html
OBiO20138 小时前
Cell | 突破AAV载体容量限制!路中华/姜玉武/刘太安团队开发AAVLINK系统实现大基因递送
笔记
智者知已应修善业9 小时前
【51单片机2个按键控制流水灯运行与暂停】2023-9-6
c++·经验分享·笔记·算法·51单片机
sakiko_9 小时前
UIKit学习笔记5-使用UITableView制作聊天页面
笔记·学习·swift·uikit
Alice-YUE10 小时前
【js高频八股】防抖与节流
开发语言·前端·javascript·笔记·学习·ecmascript
小陈phd11 小时前
TensorRT 入门完全指南(一)——从核心定义到生态工具全解析
人工智能·笔记
是上好佳佳佳呀11 小时前
【前端(十一)】JavaScript 语法基础笔记(多语言对比)
前端·javascript·笔记
handler0112 小时前
Linux 内核剖析:进程优先级、上下文切换与 O(1) 调度算法
linux·运维·c语言·开发语言·c++·笔记·算法
其实防守也摸鱼13 小时前
CTF密码学综合教学指南--第四章
网络·笔记·安全·网络安全·密码学·ctf
05候补工程师14 小时前
【ROS 2 具身智能】Gazebo 仿真避坑指南:从“幽灵机器人”到传感器数据流打通
人工智能·经验分享·笔记·ubuntu·机器人