1. 内存空间限定符(决定数据住哪里)
-
__shared__:声明在 共享内存 中。它位于片上,速度极快(比全局内存快 ~100 倍),仅对同一个线程块(Block)内的所有线程可见,用于线程间通信和数据重用。声明时加extern可动态分配大小(即<<<>>里的第 3 个参数)。 -
__constant__:声明在 常量内存 中。只读,有缓存加速,对所有网格(Grid)中的所有线程可见。适合存储所有线程都会用到的只读系数(如滤波器权重),容量仅 64KB。 -
__managed__:声明在 托管内存 中。这是最"省事"的限定词,CPU 和 GPU 都能直接通过指针访问,数据在底层自动迁移。适合简化复杂数据结构(如链表)的传输,但需注意隐式同步带来的性能开销。
实例 __shared__ ------ 线程块内共享缓存(数组归约求和)
cpp
__global__ void sum_reduce_kernel(const float* input, float* output, int n) {
// 声明静态共享内存(大小固定,这里假设 Block 大小为 256)
__shared__ float sdata[256];
int idx = threadIdx.x + blockIdx.x * blockDim.x;
int tid = threadIdx.x;
// 加载数据到共享内存(合并访问)
sdata[tid] = (idx < n) ? input[idx] : 0.0f;
__syncthreads(); // 确保所有线程都加载完毕
// 循环归约(树形加法)
for (int stride = blockDim.x / 2; stride > 0; stride >>= 1) {
if (tid < stride) {
sdata[tid] += sdata[tid + stride];
}
__syncthreads(); // 每轮归约后同步,确保数据一致
}
// 将当前 Block 的最终结果写回全局内存
if (tid == 0) {
output[blockIdx.x] = sdata[0];
}
}
关键点 :
__shared__让数据留在片上,延迟从 ~400 周期(全局)降到 ~5 周期(共享),且__syncthreads()是它的"黄金搭档"。
实例 :__constant__ ------ 只读常量缓存(滤波器系数)
场景:所有线程共享一组固定的滤波系数,存于常量内存(64KB 限制)并享受专用缓存加速。
cpp
// 在文件作用域声明(全局可见)
__constant__ float filter_coeff[5] = {0.1f, 0.2f, 0.4f, 0.2f, 0.1f};
__global__ void convolution_kernel(const float* input, float* output, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < n && idx >= 2) {
float sum = 0.0f;
// 所有线程并发读取常量内存(有缓存,且广播给 Warp 内线程)
for (int i = -2; i <= 2; ++i) {
sum += input[idx + i] * filter_coeff[i + 2];
}
output[idx] = sum;
}
}
关键点 :若在 CPU 端修改该数组,需用
cudaMemcpyToSymbol(filter_coeff, host_coeff, sizeof(host_coeff))。
实例 :__managed__ ------ 自动迁移内存(简化链表/指针结构)
场景 :让 CPU 和 GPU 共享同一个复杂结构体指针,免去手动 cudaMemcpy。
cpp
// 声明托管内存变量(设备端和主机端均可直接读写)
__device__ __managed__ int managed_data[100];
__global__ void add_kernel() {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < 100) {
managed_data[idx] *= 2; // GPU 直接修改
}
}
int main() {
// CPU 直接初始化(无需 cudaMemcpy)
for (int i = 0; i < 100; ++i) managed_data[i] = i;
// 启动内核(数据会自动迁移到 GPU)
add_kernel<<<1, 100>>>();
cudaDeviceSynchronize(); // 必须同步,等待自动迁移完成
// CPU 直接读取结果(GPU 修改后自动迁回)
printf("managed_data[10] = %d\n", managed_data[10]); // 输出 20
}
关键点:极大简化编码,但页错误迁移有开销,适合数据访问频率不高或指针结构复杂的场景。
2. 编译器优化提示符(决定跑多快)
-
__restrict__(极其重要):用于修饰指针,向编译器承诺:该指针是访问某块内存区域的唯一入口(不存在别名)。这允许编译器激进地优化(如向量化加载指令 LDS.128),是榨取性能最常用的手段。不加的话,编译器会保守地插入额外同步指令。 -
__forceinline__与__noinline__:强制建议编译器对__device__函数进行内联或禁止内联。默认情况下,小函数会被内联以消除调用开销;但若函数体过大,强制内联会膨胀寄存器占用,导致并行度下降,此时用__noinline__反而能提升性能。
3. 资源引导符(决定能开多少线程)
__launch_bounds__:放在__global__内核声明前,例如__launch_bounds__(256, 4)。它告诉编译器:"我将用 256 线程/块 启动,且最多占用 4 个块 的寄存器资源。"这能让编译器精确控制寄存器分配,避免因寄存器溢出导致占用率暴跌。建议在确定启动配置后加上。
实例:__restrict__ ------ 编译器优化"王牌"(指针别名消除)
场景 :向编译器承诺三个指针指向不同的内存区域,让编译器放心地将多条加载指令合并为单条向量化指令。
cpp
// 不加 __restrict__:编译器会保守地假设 a 和 b 可能指向同一地址
__global__ void add_no_restrict(const float* a, const float* b, float* c, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < n) c[idx] = a[idx] + b[idx];
}
// 加 __restrict__:性能可提升 20%~50%
__global__ void add_restrict(const float* __restrict__ a,
const float* __restrict__ b,
float* __restrict__ c, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < n) c[idx] = a[idx] + b[idx];
}
关键点 :仅当你确定 传入的
a、b、c在显存中没有重叠(非同一数组别名)时才能用。如果乱用,会导致计算结果错误,相当危险。
实例 :__forceinline__ + __launch_bounds__(高级优化组合)
场景:小工具函数强制内联消除调用开销;提前告知编译器线程配置,控制寄存器分配。
cpp
// 强制内联:消除函数调用指令(适合极小函数)
__device__ __forceinline__ float warp_reduce_add(float val) {
// 单 Warp 内归约(无需 __syncthreads)
val += __shfl_xor_sync(0xFFFFFFFF, val, 16);
val += __shfl_xor_sync(0xFFFFFFFF, val, 8);
val += __shfl_xor_sync(0xFFFFFFFF, val, 4);
val += __shfl_xor_sync(0xFFFFFFFF, val, 2);
val += __shfl_xor_sync(0xFFFFFFFF, val, 1);
return val;
}
// 告知编译器:该内核以 256 线程/块 启动,最多占据 4 个块
__global__ __launch_bounds__(256, 4) void compute_kernel(float* data, int n) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
float sum = 0.0f;
if (idx < n) sum = data[idx];
sum = warp_reduce_add(sum); // 内联展开
// ... 后续逻辑
}
关键点 :
__launch_bounds__中的参数需与实际启动的blockDim.x一致。它能防止编译器为节省寄存器而分配过多,导致 Occupancy(占用率)下降。
总结
| 限定词 | 使用时机 | 反面教材 |
|---|---|---|
__shared__ |
块内数据复用(如滑窗、归约) | 声明过大会导致动态分配失败 |
__constant__ |
所有线程共用的只读小数据 | 存放会频繁修改的变量(性能暴跌) |
__managed__ |
复杂数据结构(链表/树) | 在极致性能热路径中频繁访问(页迁移开销大) |
__restrict__ |
全局内存指针运算 | 传入同一数组的不同偏移(别名冲突) |
__forceinline__ |
小于 10 条指令的小函数 | 大函数强制内联会爆寄存器(用 __noinline__ 代替) |