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

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

相关推荐
后季暖6 小时前
langgraph笔记(2)&& fastapi笔记
笔记
小小筱筱7 小时前
LabVIEW笔记:解决周立功CAN卡驱动缺失导致的启动报错问题
笔记·labview
Wang's Blog7 小时前
PostgreSQL笔记4: 市场地位、生态全景与行业应用实践
数据库·笔记·postgresql
RainCity7 小时前
Java Swing 自定义组件库分享(十六)
java·笔记·后端
蒸蒸yyyyzwd9 小时前
cpp选手秋招准备 学习笔记day7 webserver
笔记·学习
Chen—LSN11 小时前
C语言——数据在内存中的存储
c语言·开发语言·经验分享·笔记
bitbrowser1 天前
giffgaff突然停用?先分清闲置停用和长期境外断开
笔记
九硕智慧建筑一体化厂家1 天前
数字化管控落地,直流照明构筑安全照明体系
运维·人工智能·笔记·安全·智慧城市
AI云海1 天前
天幕红尘笔记
笔记
青山是哪个青山1 天前
LangChain 学习笔记(三):LangSmith 调试与监控
笔记·学习·langchain