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

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

相关推荐
智者知已应修善业4 小时前
【proteus中lm339电压滞回比较器达到三角波转换成方波】2023-4-13
驱动开发·经验分享·笔记·硬件架构·proteus·硬件工程
551只玄猫5 小时前
新编大学德语1第三版笔记 第5课Essen und Trinken
笔记·学习笔记·德语·外语·德语a1·自学德语·新编大学德语
不只会拍照的程序猿6 小时前
《嵌入式AI筑基笔记02:Python数据类型01,从C的“硬核”到Python的“包容”》
人工智能·笔记·python
中屹指纹浏览器10 小时前
2026指纹浏览器性能瓶颈分析与优化技巧
经验分享·笔记
雷工笔记11 小时前
随笔|走!跳楼去!
笔记
就叫飞六吧11 小时前
国产数据库gbase8s安装-网盘
笔记
网络工程小王12 小时前
【大数据技术详解】——Kibana(学习笔记)
大数据·笔记·学习
努力的lpp12 小时前
2024小迪安全课程第四节复习笔记
笔记·安全
雷工笔记13 小时前
AI使用|通过AI学习物料分类编码表
笔记·学习