这段代码是一个标准的CUDA GPU内核函数,用于并行计算向量加法。让我详细解释每一部分的写法和目的:
📌 函数声明部分
c
__global__
void vector_add_gpu(float *a, float *b, float *c, int n)
__global__ 关键字的含义
- 作用:告诉编译器这是一个GPU内核函数,从CPU调用但在GPU上执行
- 调用方式 :通过
<<<grid, block>>>语法从CPU调用 - 特点 :不能有返回值(必须是
void),支持异步执行
参数设计要求
- 指针参数 :
a, b, c必须是GPU内存地址(设备指针) - 值传递 :
n是普通整数,直接传递给所有线程 - 限制:不能使用函数指针、不能是类的成员函数、不能有变长参数
🧵 线程索引计算部分
c
int index = blockIdx.x * blockDim.x + threadIdx.x;
为什么要这样计算?
CUDA使用**三维网格(Grid)和三维块(Block)**的层次结构:
Grid (整个计算空间)
├── Block 0 (包含N个线程)
├── Block 1
└── Block 2
├── Thread 0
├── Thread 1
└── Thread 2
计算公式拆解:
blockIdx.x:当前block在grid中的索引(0,1,2...)blockDim.x:每个block包含的线程数(如1024)threadIdx.x:当前线程在block内的索引(0~1023)
举例:假设每个block有256个线程
- Block 0的线程:索引0~255
- Block 1的线程:索引256~511
- Block 2的线程:索引512~767
🚶 Stride模式部分
c
int stride = blockDim.x * gridDim.x;
Stride的含义
stride = 每个block的线程数 × 总block数 = 总共的线程总数
例如:
- 每个block 256线程 × 总共128个block = stride = 32768个线程
为什么要用stride模式?
c
for (int i = index; i < n; i += stride) {
c[i] = a[i] + b[i];
}
这种模式不是 让每个线程只处理一个元素,而是处理多个元素:
工作原理:
- 线程0处理:索引 0, stride, 2×stride, 3×stride...
- 线程1处理:索引 1, stride+1, 2×stride+1...
- 线程2处理:索引 2, stride+2, 2×stride+2...
优点:
- 适配任意数组大小:n不一定能被线程总数整除
- 负载均衡:所有线程工作量相近
- 合并访问:连续的线程访问连续的内存地址,提高内存带宽利用率
📊 实际运行示例
假设配置:<<<2个block, 4个线程/block>>>
总线程数 = 2 × 4 = 8个线程
stride = 8
处理数组 n=10个元素:
线程0 (index=0): 处理 [0, 8]
线程1 (index=1): 处理 [1, 9]
线程2 (index=2): 处理 [2]
线程3 (index=3): 处理 [3]
线程4 (index=4): 处理 [4]
线程5 (index=5): 处理 [5]
线程6 (index=6): 处理 [6]
线程7 (index=7): 处理 [7]
⚠️ 写法的关键要求
1. 内存一致性
c
// ❌ 错误:不能访问主机内存
__global__ void kernel(int *host_ptr) {
int val = host_ptr[0]; // 错误!host_ptr在CPU内存
}
// ✅ 正确:必须使用设备内存
__global__ void kernel(int *dev_ptr) {
int val = dev_ptr[0]; // 正确
}
2. 线程安全
c
// 避免:多个线程写同一位置(竞态条件)
if (threadIdx.x == 0) {
c[0] = a[0] + b[0]; // 只有一个线程写,安全
}
// 危险:所有线程都写同一位置
c[0] = a[index] + b[index]; // ❌ 数据竞争!
3. 边界检查
c
for (int i = index; i < n; i += stride) { // ✅ 检查边界
c[i] = a[i] + b[i];
}
如果不检查 i < n,越界访问会导致未定义行为。
🎯 启动配置要求
c
int threads_per_block = 256; // 通常选32的倍数(warp大小)
int blocks_per_grid = (n + threads_per_block - 1) / threads_per_block;
vector_add_gpu<<<blocks_per_grid, threads_per_block>>>(d_a, d_b, d_c, n);
限制:
- 每个block最多1024线程(CUDA限制)
- block数量可以很大(最多2³¹-1)
💡 性能优化要点
- Coalesced Memory Access:stride模式确保相邻线程访问相邻地址
- Occupancy:每个SM(流多处理器)的活跃线程数影响性能
- 同步 :如果需要在block内同步,使用
__syncthreads()
这个设计模式被称为**"grid-stride loop"**,是CUDA编程的最佳实践,兼具灵活性(适配任意数组大小)和性能(合并内存访问)。