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

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

相关推荐
Lester_1101几秒前
嵌入式学习笔记 - 关于看门狗定时器的喂狗的操作放在中断还是放在主循环
笔记·单片机·学习
AA陈超1 小时前
ASC学习笔记0017:返回此能力系统组件的所有属性列表
c++·笔记·学习·ue5·虚幻引擎
谅望者2 小时前
数据分析笔记07:Python编程语言介绍
大数据·数据库·笔记·python·数据挖掘·数据分析
Cathy Bryant2 小时前
信息论(五):联合熵与条件熵
人工智能·笔记·机器学习·数学建模·概率论
谅望者3 小时前
数据分析笔记03:概率分布理论
笔记·数据分析·概率论
程序员东岸4 小时前
从零开始学二叉树(上):树的初识 —— 从文件系统到树的基本概念
数据结构·经验分享·笔记·学习·算法
谅望者4 小时前
数据分析笔记09:Python条件语循环
笔记·python·数据分析
全栈游侠4 小时前
Cortex-M3 02-地址映射
笔记
李昊哲小课4 小时前
wsl ubuntu24.04 cuda13 cudnn9 pytorch 显卡加速
人工智能·pytorch·python·cuda·cudnn
菩提树下的凡夫5 小时前
Node.js+Vue的学习笔记
笔记·学习·node.js