背景
这块知识之前没有动手画过 😄让AI帮我直接画出来 数据可能是他编的 但流程大体是对的 这样有图就看着非常舒适😌👌
GPU 数据流动完整架构图
1. GPU 物理芯片架构
┌────────────────────────────────────────────────────────────────────────────┐
│ GPU 物理芯片 │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ VRAM (8 GB GDDR6 芯片) │ │
│ │ 物理存储区域 │ │
│ │ ┌──────────┬─────────┬──────────┬──────────┬──────────┐ │ │
│ │ │Frame │Textures │Vertex │Constant │Storage │ │ │
│ │ │Buffer │ │Buffers │Buffers │Buffers │ │ │
│ │ │ │ │ │(UBO) │(SSBO) │ │ │
│ │ │ 56 MB │ 3-5 GB │ 1-2 GB │ 10 MB │500MB-2GB │ │ │
│ │ └──────────┴─────────┴──────────┴──────────┴──────────┘ │ │
│ │ │ │
│ │ 内存总线 (512-bit @ 14 Gbps) → 带宽: 448 GB/s │ │
│ └────────────────────────────┬───────────────────────────────────────────┘
│ │
│ │ 通过内存控制器
│ │
│ ┌────────────────────────────▼───────────────────────────────────────────┐
│ │ L2 Cache (共享) │
│ │ 容量: 4 MB, 带宽: 2-4 TB/s │
│ │ ┌──────────┬───────────┬──────────┬──────────┬─────────────┐ │
│ │ │Texture │ Vertex │ Constant │ Storage │Generic │ │
│ │ │Cache │ Cache │ Cache │ Cache │Cache │ │
│ │ │ │ │ (UBO专用)│ (SSBO) │ │ │
│ │ │ 1.2 MB │ 0.5 MB │ 0.3 MB │ 0.5 MB │ 1.5 MB │ │
│ │ └──────────┴───────────┴──────────┴──────────┴─────────────┘ │
│ └────────┬────────────┬────────────┬────────────┬──────────────────────────┘
│ │ │ │ │
│ ┌────────▼──┐ ┌──────▼───┐ ┌────▼─────┐ ┌──▼────────┐
│ │ SM 0 │ │ SM 1 │ │ SM 2 │ │ SM 3 │ ... (共 46 个 SM)
│ │ │ │ │ │ │ │ │
│ │ ┌───────────────────────────────────────────────────┐│
│ │ │ Warp Scheduler (硬件调度器) ││
│ │ │ ┌─────────────────────────────────────────────┐ ││
│ │ │ │ Warp 0: Pixel Shader (32 threads) - Ready │ ││
│ │ │ │ Warp 1: Vertex Shader (32 threads) - Ready │ ││
│ │ │ │ Warp 2: Pixel Shader (32 threads) - Stall │ ││ ← 等待纹理
│ │ │ │ Warp 3: Compute (32 threads) - Ready │ ││
│ │ │ └─────────────────────────────────────────────┘ ││
│ │ └───────────────────────────────────────────────────┘│
│ │ │
│ │ ┌──────────────────────────────────────────────────┐ │
│ │ │ L1 Cache / Shared Memory │ │
│ │ │ 容量: 128 KB (可配置 L1/Shared 比例) │ │
│ │ │ 带宽: 10+ TB/s │ │
│ │ │ ┌────────────┬──────────┬──────────────┐ │ │
│ │ │ │L1 Data │Texture L1│Constant Cache│ │ │
│ │ │ │Cache │ │(专用广播) │ │ │
│ │ │ │ 48 KB │ 48 KB │ 32 KB │ │ │
│ │ │ └────────────┴──────────┴──────────────┘ │ │
│ │ └────────────┬─────────────────────────────────────┘ │
│ │ │ │
│ │ ┌────────────▼─────────────────────────────────────┐ │
│ │ │ Texture Units (纹理单元) │ │
│ │ │ - 4 个独立的纹理采样器 │ │
│ │ │ - 过滤硬件 (双线性/三线性/各向异性) │ │
│ │ │ - 地址计算 │ │
│ │ └──────────────────────────────────────────────────┘ │
│ │ │ │
│ │ ┌────────────▼─────────────────────────────────────┐ │
│ │ │ CUDA Cores (FP32 ALU) │ │
│ │ │ 128 个计算单元 │ │
│ │ │ ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐ │ │
│ │ │ │C0││C1││C2││C3││C4││C5││C6││C7│ ... x16 行 │ │
│ │ │ └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘ │ │
│ │ └──────────────────────────────────────────────────┘ │
│ │ │ │
│ │ ┌────────────▼─────────────────────────────────────┐ │
│ │ │ Register File (寄存器堆) │ │
│ │ │ 容量: 256 KB │ │
│ │ │ 每个线程: 255 个 32-bit 寄存器 │ │
│ │ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ │ Thread 0: r0-r254 │ │ │
│ │ │ │ Thread 1: r0-r254 │ │ │
│ │ │ │ ... │ │ │
│ │ │ │ Thread 31: r0-r254 │ │ │
│ │ │ └──────────────────────────────────────────┘ │ │
│ │ └──────────────────────────────────────────────────┘ │
│ └────────────────────────────────────────────────────────┘
└─────────────────────────────────────────────────────────────┘
2. 数据 Fetch 的完整流程
执行代码:float4 p12 = TexelLoad2D(curveData, curveLoc);
第 1 步:Pixel Shader 线程执行到这一行
┌─────────────────────────────────────────┐
│ SM 内的 Warp 0 (32 个线程) │
│ │
│ Thread 0: curveLoc = (100, 50) │
│ Thread 1: curveLoc = (101, 50) │
│ Thread 2: curveLoc = (102, 50) │
│ ... │
│ Thread 31: curveLoc = (131, 50) │
│ │
│ 每个线程需要读取不同的纹素 │
│
│ 发起 32 个纹理读取请求
│
▼
第 2 步:Texture Unit 接收请求
┌─────────────────────────────────────────┐
│ Texture Unit (纹理单元) │
│ │
│ 1. 计算物理地址: │
│ curveLoc (100, 50) → VRAM 地址 │
│ = baseAddress + (50 * 4096 + 100) * 16 bytes
│ │
│ 2. 批量合并请求 (Coalescing): │
│ 32 个连续地址 → 合并为几个内存事务 │
│ │
│ 3. 检查 L1 Texture Cache │
│
│ Cache Miss (缓存未命中)
│
▼
第 3 步:查询 L2 Cache
┌─────────────────────────────────────────┐
│ L2 Cache (4 MB 共享缓存) │
│ │
│ Hash 查找: Address → Cache Line │
│ │
│ 缓存行大小: 128 bytes │
│ 一个缓存行包含: 128/16 = 8 个纹素 │
│ │
│ 检查结果: 部分命中, 部分未命中 │
│
│ 未命中的部分
│
▼
第 4 步:访问 VRAM
┌─────────────────────────────────────────┐
│ Memory Controller (内存控制器) │
│ │
│ 1. 发起 GDDR6 读取事务 │
│ 2. 内存总线传输 (512-bit 宽) │
│ 3. 延迟: ~400 clock cycles │
│ │
│ ┌─────────────────────────────────┐ │
│ │ VRAM - Texture 区域 │ │
│ │ curveTexture (4096x4096 RGBA32F) │ │
│ │ │ │
│ │ 地址 0x12340000: [p1.x, p1.y, p2.x, p2.y]
│ │ 地址 0x12340010: [p1.x, p1.y, p2.x, p2.y]
│ │ ... │ │
│ └─────────────────────────────────┘ │
│
│ 数据返回
│
▼
第 5 步:数据回填 Cache
┌─────────────────────────────────────────┐
│ 数据流向: │
│ VRAM → Memory Controller → L2 Cache │
│ → L1 Texture Cache → Registers │
│ │
│ L2 Cache 更新: │
│ 存储这 8 个纹素到 Cache Line │
│ │
│ L1 Cache 更新: │
│ 存储当前 Warp 需要的纹素 │
│
│
▼
第 6 步:Warp 恢复执行
┌─────────────────────────────────────────┐
│ Warp Scheduler │
│ │
│ Warp 0: Stall → Ready │
│ (数据已到达寄存器) │
│ │
│ 调度 Warp 0 回到 CUDA Cores 继续执行 │
│
▼
第 7 步:继续执行后续指令
┌─────────────────────────────────────────┐
│ CUDA Cores │
│ │
│ Thread 0 寄存器: │
│ r0 = p12.x (从 L1 Cache 加载) │
│ r1 = p12.y │
│ r2 = p12.z │
│ r3 = p12.w │
│ │
│ 执行下一条指令: │
│ float2 a = p12.xy - p12.zw * 2.0 + p3; │
│ ↑ 在寄存器中直接运算 │
3. 关键优化:内存访问合并 (Memory Coalescing)
┌──────────────────────────────────────────────────────────┐
│ 情况 1: 好的访问模式 (Coalesced) │
│ │
│ Warp 中 32 个线程访问连续地址: │
│ Thread 0: Address 0x1000 ┐ │
│ Thread 1: Address 0x1010 │ │
│ Thread 2: Address 0x1020 │ 合并为 1-2 个内存事务 │
│ ... │ │
│ Thread 31: Address 0x11F0 ┘ │
│ │
│ 带宽利用率: 100% │
│ 延迟: ~400 cycles (一次内存访问) │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ 情况 2: 坏的访问模式 (Uncoalesced) │
│ │
│ Warp 中 32 个线程访问随机地址: │
│ Thread 0: Address 0x1000 ┐ │
│ Thread 1: Address 0xF000 │ │
│ Thread 2: Address 0x5000 │ 需要 32 个独立内存事务 │
│ ... │ │
│ Thread 31: Address 0xA000 ┘ │
│ │
│ 带宽利用率: 3-10% (大量浪费) │
│ 延迟: ~400 * 32 = 12,800 cycles │
└──────────────────────────────────────────────────────────┘
4. GPU Buffer 类型详解
Constant Buffer (UBO) vs Storage Buffer (SSBO)
┌────────────────────────────────────────────────────────────────┐
│ Buffer 类型对比 │
├─────────────────┬──────────────────┬──────────────────────────┤
│ 特性 │ Constant Buffer │ Storage Buffer (SSBO) │
│ │ (UBO) │ │
├─────────────────┼──────────────────┼──────────────────────────┤
│ OpenGL 名称 │ Uniform Buffer │ Shader Storage Buffer │
│ │ Object │ Object │
├─────────────────┼──────────────────┼──────────────────────────┤
│ Vulkan 名称 │ Uniform Buffer │ Storage Buffer │
├─────────────────┼──────────────────┼──────────────────────────┤
│ DirectX 名称 │ Constant Buffer │ Structured Buffer │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 大小限制 │ 64 KB (OpenGL) │ 128 MB+ (几乎无限) │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 读写权限 │ 只读 │ 可读写 │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 访问模式 │ Uniform (统一) │ Random (随机) │
│ │ 所有线程读相同值 │ 每线程读不同值 │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 硬件优化 │ 专用 Constant │ 通用 L1/L2 Cache │
│ │ Cache + 广播机制 │ 需要访问合并优化 │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 访问延迟 │ ~5 cycles │ ~30-400 cycles │
│ │ (Cache 命中) │ (取决于 Cache 命中率) │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 内存对齐 │ 16 字节 (std140) │ 更灵活 (std430) │
├─────────────────┼──────────────────┼──────────────────────────┤
│ 典型用途 │ - 变换矩阵 │ - 粒子系统 │
│ │ - 光照参数 │ - 大型数据数组 │
│ │ - 材质参数 │ - Compute Shader 输出 │
│ │ - 时间/帧参数 │ - 动态网格数据 │
└─────────────────┴──────────────────┴──────────────────────────┘
硬件访问路径对比
═════════════════════════════════════════════════════════════════
Constant Buffer (UBO) 访问 - 广播优化
═════════════════════════════════════════════════════════════════
Shader Code:
mat4 vp = ubo.viewProjection; // 所有线程读相同值
↓
┌─────────────────────────────────────────┐
│ Warp (32 threads) │
│ Thread 0-31: 都需要 viewProjection │
└───────────────┬─────────────────────────┘
│
│ Uniform Load (单次读取 + 广播)
↓
┌───────────────────────────────────────────┐
│ Constant Cache (SM 内, 32 KB) │
│ - 延迟: ~5 cycles │
│ - 硬件广播: 1 次读取 → 32 个线程 │
│ - 带宽消耗: 64 bytes (矩阵大小) │
└───────────────┬───────────────────────────┘
│ Cache Miss
↓
┌───────────────────────────────────────────┐
│ L2 Constant Cache (0.3 MB) │
└───────────────┬───────────────────────────┘
│
↓
┌───────────────────────────────────────────┐
│ VRAM - Constant Buffer 区域 │
└───────────────────────────────────────────┘
性能: 极快,1 次内存访问服务所有线程
═════════════════════════════════════════════════════════════════
Storage Buffer (SSBO) 访问 - 无广播
═════════════════════════════════════════════════════════════════
Shader Code:
vec4 pos = particles.positions[id]; // 每线程不同 id
↓
┌─────────────────────────────────────────┐
│ Warp (32 threads) │
│ Thread 0: positions[0] │
│ Thread 1: positions[1] │
│ ... │
│ Thread 31: positions[31] │
└───────────────┬─────────────────────────┘
│
│ 32 个独立的 Load 请求
↓
┌───────────────────────────────────────────┐
│ L1 Data Cache (48 KB) │
│ - 延迟: ~30 cycles │
│ - 需要合并: 连续地址 → 1-2 次内存事务 │
│ - 随机地址 → 32 次内存事务 ⚠️ │
└───────────────┬───────────────────────────┘
│ Cache Miss
↓
┌───────────────────────────────────────────┐
│ L2 Storage Cache (0.5 MB) │
└───────────────┬───────────────────────────┘
│
↓
┌───────────────────────────────────────────┐
│ VRAM - Storage Buffer 区域 │
└───────────────────────────────────────────┘
性能: 取决于访问模式
- 连续访问: 较快 (Coalesced)
- 随机访问: 慢 (Uncoalesced)
代码示例对比
glsl
// ================== Constant Buffer (UBO) ==================
layout(binding = 0) uniform PerFrame {
mat4 viewProjection; // 64 bytes
mat4 model; // 64 bytes
vec4 cameraPos; // 16 bytes
vec4 lightDir; // 16 bytes
float time; // 4 bytes
float padding[3]; // 12 bytes (对齐到 16)
// 总大小: 176 bytes (远小于 64 KB 限制)
} ubo;
void main() {
// 所有像素读取相同的矩阵 → 硬件广播优化
vec4 worldPos = ubo.model * vec4(position, 1.0);
gl_Position = ubo.viewProjection * worldPos;
// 访问模式: Uniform Load
// 性能: 极快 (~5 cycles)
}
// ================== Storage Buffer (SSBO) ==================
struct Particle {
vec4 position;
vec4 velocity;
vec4 color;
float lifetime;
};
layout(binding = 1) buffer ParticleData {
Particle particles[]; // 可以有 100 万个粒子
// 总大小: 1,000,000 × 52 bytes = 52 MB
} ssbo;
void main() {
uint id = gl_GlobalInvocationID.x;
// 每个线程读取不同的粒子 → 无广播
Particle p = ssbo.particles[id];
// 物理更新
p.velocity.xyz += vec3(0, -9.8, 0) * deltaTime;
p.position.xyz += p.velocity.xyz * deltaTime;
// 写回 (Storage Buffer 支持写入)
ssbo.particles[id] = p;
// 访问模式: Random Load/Store
// 性能: 取决于 id 的连续性
// - id 连续 (0,1,2,3...) → 快 (Coalesced)
// - id 随机 → 慢 (Uncoalesced)
}
性能对比实例
场景: 1024 个线程并行执行
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Constant Buffer 访问 (读取 viewProjection 矩阵)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
mat4 vp = ubo.viewProjection;
内存事务: 1 次 (广播给所有 1024 个线程)
延迟: ~5 cycles
带宽消耗: 64 bytes
效率: ⭐⭐⭐⭐⭐ (100%)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Storage Buffer 访问 (最坏情况 - 随机访问)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
vec4 vel = ssbo.particles[randomId].velocity;
内存事务: 1024 次 (每个线程独立)
延迟: ~400 cycles × 1024 = 409,600 cycles
带宽消耗: 16 KB (1024 × 16 bytes)
效率: ⭐ (3-10%)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Storage Buffer 访问 (最好情况 - 连续访问)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
uint id = gl_GlobalInvocationID.x; // 0, 1, 2, 3...
vec4 vel = ssbo.particles[id].velocity;
内存事务: 16-32 次 (硬件合并)
延迟: ~50-100 cycles
带宽消耗: 16 KB
效率: ⭐⭐⭐⭐ (80-90%)
最佳实践建议
✅ 使用 Constant Buffer (UBO) 的场景:
1. 所有线程需要相同的数据
- 变换矩阵 (MVP)
- 相机参数
- 光源参数
- 材质属性
2. 数据较小 (< 64 KB)
3. 每帧/每个 Draw Call 更新一次
4. 只读访问
✅ 使用 Storage Buffer (SSBO) 的场景:
1. 每个线程需要不同的数据
- 粒子数组
- 顶点变形数据
- 实例化渲染的变换矩阵数组
2. 数据量大 (> 64 KB)
3. 需要写入数据
- Compute Shader 输出
- GPU 端数据更新
4. 动态数组大小
⚠️ Storage Buffer 优化技巧:
1. 保证线程访问连续的内存地址
✅ particles[gl_GlobalInvocationID.x]
❌ particles[hash(gl_GlobalInvocationID.x)]
2. 使用 SoA (Structure of Arrays) 代替 AoS
✅ positions[], velocities[], colors[]
❌ particles[].position, particles[].velocity
3. 对齐数据到 16 字节边界
4. 批量读写,减少内存事务次数
5. 完整数据流总结
✅ 性能影响因素准确表述: "对于内存密集型程序,Cache 命中率是主要因素之一;但对于计算密集型程序,Cache 命中率影响较小,反而是计算吞吐量、占用率和分支效率更重要"
GPU 性能 = min(
内存带宽 × Cache命中率 × Coalescing效率,
计算吞吐量 × 占用率 × (1 - Divergence损失),
纹理单元吞吐量,
ROP 吞吐量
)
性能由最慢的环节决定(木桶原理)
CPU → GPU 数据传输:
应用程序
↓ CreateBuffer / UpdateBuffer
驱动 (内存管理)
↓ PCIe 传输 (16-32 GB/s)
VRAM (物理存储)
GPU 内部数据流:
VRAM (448 GB/s)
↓
L2 Cache (2-4 TB/s)
↓
L1 Cache / Constant Cache (10+ TB/s)
↓
Registers (每个线程私有)
↓
ALU 计算单元 (CUDA Cores)
关键性能因素:
1. Cache 命中率 (最重要)
2. 内存访问合并 (Coalescing)
3. Warp Divergence (分支)
4. Register Pressure (寄存器使用)
5. 内存带宽利用率
优化 GPU 性能的优先级:
1️⃣ 算法优化 (最高优先级)
- 减少不必要的计算
- 减少不必要的内存访问
⚡ 影响: 10-100 倍
2️⃣ 内存访问模式优化
- Coalescing (合并访问)
- SoA vs AoS
- 对齐
⚡ 影响: 2-30 倍
3️⃣ 占用率优化
- 减少寄存器使用
- 减少 Shared Memory 使用
- 增加 Warp 数量
⚡ 影响: 1.5-3 倍
4️⃣ Cache 命中率优化
- 数据局部性
- Tile/Block 策略
⚡ 影响: 1.5-5 倍
5️⃣ 分支优化
- 减少 Warp Divergence
- 使用 __ballot, __any, __all
⚡ 影响: 1.2-2 倍
我的总结
需要拉数据尽量一次拉够 拉取地址尽量连续 逻辑能少用点寄存器就少用点(刚好吃满最好 太少不行) 哈哈
AI美化后
GPU Shader 优化三原则:
1. 数据尽量连续访问
→ 相邻线程访问相邻地址
→ 使用 SoA 数据布局
→ 影响最大 (10-30倍)
2. 数据尽量一次拉够
→ 批量读取连续数据
→ 复用寄存器中的数据
→ 但不要拉取用不到的数据
3. 寄存器使用要平衡
→ 不要过多 (降低占用率)
→ 也不要过少 (导致 Spill 或重复计算)
→ 目标: 占用率 50-100%,寄存器 20-40 个/线程