CUDA Cache Streaming 优化指南
目录
引言
在GPU编程中,内存访问往往是性能瓶颈。NVIDIA GPU提供了Cache Streaming机制,通过PTX汇编指令可以精确控制数据的缓存行为。本文将深入解析这一优化技术,从理论原理到实际应用,帮助开发者在合适的场景下获得显著的性能提升。
技术背景
GPU内存层次结构
现代NVIDIA GPU的内存层次结构从快到慢依次为:
- 寄存器(Registers)
- 共享内存(Shared Memory)
- L1缓存(L1 Cache)
- L2缓存(L2 Cache)
- 全局内存(Global Memory)
传统的内存访问路径:
全局内存 → L2缓存 → L1缓存 → 寄存器
缓存污染问题
在某些应用场景中,大量只访问一次的数据会填满L1缓存,导致:
- 真正需要重复访问的热点数据被驱逐
- 缓存命中率下降
- 整体性能降低
实际案例:稀疏矩阵向量乘法(SpMV)
以DASP算法为例,在稀疏矩阵向量乘法中存在两类不同的数据访问模式:
// 稀疏矩阵元素:大数据量,只访问一次
for (int i = start; i < end; i++) {
float matrix_val = matrix_values[i]; // 只访问一次的大数组
int col_idx = column_indices[i]; // 只访问一次的索引数组
// 向量元素:小数据集,被多次访问
float vector_val = vector[col_idx]; // 可能被多个矩阵行访问
result += matrix_val * vector_val;
}
在这种情况下,如果矩阵数据污染了L1缓存,会降低向量数据的缓存命中率。
Cache Streaming 原理
基本概念
Cache Streaming(.cs)是NVIDIA GPU提供的一种特殊内存访问模式,其特点是:
- 跳过L1缓存:数据直接从L2缓存读取到寄存器
- 避免缓存污染:不会影响L1缓存中的其他数据
- 适合流式访问:适用于大数据量的顺序或单次访问
内存访问路径对比
普通访问模式:
全局内存 → L2缓存 → L1缓存 → 寄存器
Cache Streaming模式:
全局内存 → L2缓存 → 寄存器
硬件支持
Cache Streaming需要硬件支持,主要支持的GPU架构:
- Pascal (GTX 10系列)
- Volta (Tesla V100)
- Turing (RTX 20系列)
- Ampere (RTX 30系列, A100)
- Hopper (H100)
- Ada Lovelace (RTX 40系列)
PTX指令详解
基本语法
Cache Streaming通过PTX汇编指令实现,基本语法为:
ld.global.cs.type destination, [address];
st.global.cs.type [address], source;
数据类型支持
| PTX类型 | C++类型 | 描述 |
|---|---|---|
.f32 | float | 32位浮点数 |
.f64 | double | 64位浮点数 |
.s32 | int | 32位有符号整数 |
.u32 | unsigned int | 32位无符号整数 |
.s64 | long long | 64位有符号整数 |
.u64 | unsigned long long | 64位无符号整数 |
内联汇编实现
// 双精度浮点数加载
__device__ __forceinline__ double load_double_cs(const double* addr) {
double r;
asm volatile("ld.global.cs.f64 %0, [%1];" : "=d"(r) : "l"(addr));
return r;
}
// 单精度浮点数加载
__device__ __forceinline__ float load_float_cs(const float* addr) {
float r;
asm volatile("ld.global.cs.f32 %0, [%1];" : "=f"(r) : "l"(addr));
return r;
}
// 32位整数加载
__device__ __forceinline__ int load_int_cs(const int* addr) {
int r;
asm volatile("ld.global.cs.s32 %0, [%1];" : "=r"(r) : "l"(addr));
return r;
}
// 双精度浮点数存储
__device__ __forceinline__ void store_double_cs(double* addr, double val) {
asm volatile("st.global.cs.f64 [%0], %1;" :: "l"(addr), "d"(val));
}
// 单精度浮点数存储
__device__ __forceinline__ void store_float_cs(float* addr, float val) {
asm volatile("st.global.cs.f32 [%0], %1;" :: "l"(addr), "f"(val));
}
内联汇编约束说明
| 约束符 | 含义 | 用途 |
|---|---|---|
"=d" | 输出约束,双精度寄存器 | double类型输出 |
"=f" | 输出约束,单精度寄存器 | float类型输出 |
"=r" | 输出约束,通用寄存器 | int类型输出 |
"l" | 输入约束,long类型(地址) | 指针输入 |
"d" | 输入约束,双精度寄存器 | double类型输入 |
"f" | 输入约束,单精度寄存器 | float类型输入 |