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

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

相关推荐
悠哉悠哉愿意2 小时前
【数据结构与算法学习笔记】双指针
数据结构·笔记·python·学习·算法
常州晟凯电子科技3 小时前
君正T32开发笔记之IVSP版本环境搭建和编译
人工智能·笔记·物联网
sealaugh323 小时前
AI(学习笔记第九课) 使用langchain的MultiQueryRetriever和indexing
人工智能·笔记·学习
张书名4 小时前
《强化学习数学原理》学习笔记7——从贝尔曼最优方程得到最优策略
笔记·学习
报错小能手4 小时前
linux学习笔记(11)fork详解
linux·笔记·学习
我命由我123454 小时前
Photoshop - Photoshop 工具栏(1)移动工具
笔记·学习·ui·职场和发展·求职招聘·职场发展·photoshop
聪明的笨猪猪7 小时前
Java 高并发多线程 “基础”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
我命由我1234512 小时前
Photoshop - Photoshop 工具从工具栏消失
笔记·学习·ui·职场和发展·职场发展·photoshop·ps
zhangrelay19 小时前
操作系统全解析:Windows、macOS与Linux的深度对比与选择指南(AI)
linux·笔记·学习