CUDA 开启的 GPU 编程
time:2025_12_26
1. 工程与编译(CMake / nvcc)
1.1 最小 CMake 工程
1 | cmake_minimum_required(VERSION 3.18) |
1.2 推荐的基础错误检查宏
避免依赖 sample 的 helper_cuda.h,直接自带一个最小版:
1 |
2. CUDA 基础:函数修饰符与执行位置
2.1 __global__ / __device__ / __host__
__global__:核函数(kernel)- 在 GPU 上并行执行
- 由主机端(CPU)发起
<<<...>>> - 返回类型必须为
void(通常通过指针写回结果)
__device__:设备函数- 在 GPU 上执行
- 只能从 device/global 调用
__host__:主机函数- 在 CPU 上执行
- 未标注的普通函数默认就是 host
组合:
__host__ __device__:同一函数在 CPU/GPU 两侧都可用(注意 device 侧不支持完整的 C++ 标准库能力)
2.2 __CUDA_ARCH__(区分 device/host 编译路径)
__CUDA_ARCH__只在 device 编译路径中定义,值为计算能力架构号(例如 750/800 等)- 常用于同一个函数在 host/device 的条件编译
1 | __host__ __device__ inline int where_am_i() { |
2.3 constexpr 与 device 代码
- 如需更宽松的
constexpr在 device 侧工作,常用--expt-relaxed-constexpr - device 侧 lambda 扩展常用
--expt-extended-lambda
3. Kernel 启动、线程块模型与索引
3.1 Kernel 启动语法
1 | kernel<<<grid, block, shared_bytes, stream>>>(args...); |
- 常用:
<<<grid, block>>> shared_bytes:动态共享内存字节数(默认 0)stream:CUDA 流(默认 0)
3.2 线程/块索引
threadIdx.{x,y,z}:线程在块内索引blockIdx.{x,y,z}:块在网格内索引blockDim.{x,y,z}:每块线程数维度gridDim.{x,y,z}:网格块数维度
3.3 典型打印示例(便于理解执行模型)
1 | #include <cstdio> |
3.4 Grid-Stride Loop(通用遍历范式)
适用于任意大小数据与任意 grid/block 配置:
1 | __global__ void work(int* a, int n) { |
4. 同步与错误处理
4.1 CPU/GPU 默认异步
kernel launch 对 host 来说通常是异步的
常用同步:
cudaDeviceSynchronize():等待当前设备上所有已提交工作完成cudaStreamSynchronize(stream):等待某个流完成
4.2 推荐的 launch 后检查模板
1 | kernel<<<grid, block>>>(...); |
5. 内存管理(Host/Device/Unified)
5.1 经典模式:cudaMalloc + cudaMemcpy
1 | #include <cstdio> |
5.2 统一内存(Unified Memory):cudaMallocManaged
- 一份指针同时可被 CPU/GPU 访问
- 常配合同步;性能敏感时可用预取提升稳定性
1 | #include <cstdio> |
5.3 预取(Prefetch)与驻留优化(进阶但常用)
1 | int dev = 0; |
6. C++ 封装:RAII 与可复用接口
6.1 RAII 管理 Unified Memory 指针(简单、可靠、适合入门)
1 |
|
6.2 结合 kernel 使用
1 | __global__ void init(int* a, int n) { |
allocator 方式也可把 unified memory 接入
std::vector,但 allocator 细节较多;建议先把 RAII 指针与.data()传参掌握牢。
7. Thrust 库:容器与算法(高层 CUDA)
7.1 常用容器
thrust::host_vector<T>:主机端 vectorthrust::device_vector<T>:设备端 vector- 通过赋值可触发 H2D / D2H 拷贝(更准确地说:构造/赋值会在 host/device 容器之间进行数据迁移)
7.2 AXPY 示例(device_vector + 自写 kernel)
1 | #include <thrust/host_vector.h> |
8. 原子操作(Atomic)
8.1 常用原子
atomicAdd / atomicSubatomicAnd / atomicOr / atomicXoratomicMin / atomicMaxatomicCAS:Compare-And-Swap,可用于构造自定义原子操作
8.2 用 CAS 实现自定义原子加
1 | __device__ __forceinline__ int my_atomic_add(int* dst, int val) { |
8.3 朴素并行求和(全局原子累加)
1 | __global__ void parallel_sum(int* sum, const int* arr, int n) { |
9. 线程块与共享内存(Shared Memory)
9.1 核心概念
__shared__:块内共享内存(一个 block 内所有线程可见)__syncthreads():块内同步屏障(必须保证同一 block 的线程都能到达)
共享内存常用于:
- 块内复用数据(减少 global memory 访问)
- 块内归约(reduce)
- tile-based 计算(矩阵乘、卷积、图像算子)
9.2 块内归约:每块只做一次全局原子
1 | #include <cuda_runtime.h> |
启动方式(动态共享内存大小):
1 | int threads = 256; |
9.3 Tile 示例:2D 图像 3x3 均值滤波(共享内存加速范式)
适用于图像/矩阵类任务(tile + halo):
1 | #include <cuda_runtime.h> |
共享内存大小(字节):
1 | dim3 block(16, 16); |
10. CUDA Streams 与异步拷贝
10.1 为什么需要 streams
默认 stream(stream 0)会形成较强的串行依赖
多 stream 可以实现:
- H2D 拷贝与 kernel 重叠
- 多批次流水线(pipeline)
- 与
cudaMemcpyAsync配合提升吞吐
10.2 pinned(页锁定)主机内存:提升异步拷贝效率
cudaMallocHost/cudaFreeHost- pinned 内存更利于 DMA,
cudaMemcpyAsync才更有意义
10.3 基本模板:两条 stream 流水搬运
1 | #include <cstdio> |
10.4 事件计时(event timing)
用于测量 GPU 端耗时:
1 | cudaEvent_t st, ed; |