1. GPU 内存访问机制
GPU 的特点:
-
每个 GPU 核心(线程)会处理一个小任务(比如一个粒子或一个像素)
-
这些线程通常是 成组执行 的(称为 warp 或 wavefront,NVIDIA 上是 32 个线程一组)
-
这些线程会同时请求内存,如果它们访问的是连续的内存地址,GPU 会把这些请求合并(coalesced),一次性读入一块连续内存
-
如果访问不连续,GPU 需要多次访问,效率大幅下降
1. GPU 并行执行的特点
GPU 的线程是 成组执行 (NVIDIA 叫 warp,AMD 叫 wavefront),比如 NVIDIA 是 32 个线程一组:
-
这一组线程同时执行同一条指令(SIMD/Single Instruction Multiple Data)
-
如果所有线程都做同样的操作,执行非常快
-
如果有条件分支 (
if),情况就复杂了
2. 为什么 if 会慢
假设你有这样一个代码片段:
if (condition)
doA();
else
doB();
-
如果条件在 warp 内所有线程一致 → GPU 一次执行
doA或doB,效率正常 -
如果条件不同(warp divergence) → GPU 需要:
-
先执行满足条件的线程
-
再执行不满足条件的线程
-
最后合并结果
-
这个过程叫 warp divergence ,会导致部分线程闲置,吞吐量下降。
2. 分支情况下(warp divergence)
假设 warp 内线程条件不一致:
if (condition)
doA();
else
doB();
-
GPU 内部处理方式:
-
执行满足条件的线程
doA()→ 其他线程被掩盖(idle) -
执行不满足条件的线程
doB()→ 前面 idle 的线程现在执行,原来的 doA 线程 idle -
恢复线程状态,合并结果
-
结果:每条指令可能要执行两次(甚至更多,如果条件更多),导致部分线程在每次执行时闲置 → 实际吞吐量下降。
3. 性能消耗量化
假设:
-
没有分支:warp 执行一次操作,32 个线程都忙 → 100% 利用率
-
有分支:16 个线程做
doA,16 个做doB
那么:
-
执行
doA:16 个线程有效,16 个线程 idle → 利用率 50% -
执行
doB:16 个线程有效,16 个线程 idle → 利用率 50% -
平均下来,这条指令的 warp 利用率 = 50%
可以看出,理论上 warp divergence 会直接降低吞吐率,性能消耗很明显,尤其在大量分支和大 warp 的情况下。
为什么很多代码只用 dot(a,a)
在很多图形学 / 游戏 / shader 里,经常直接用 dot(a,a),原因是:
-
不用 sqrt,计算更快
-
如果只是比较距离大小,用平方就够了
平方非常便宜,sqrt 相对贵很多,但"贵多少"取决于硬件和编译器。总体规律在 CPU / GPU 上都差不多。
1️⃣ 运算成本对比(大致级别)
| 运算 | 大致成本 |
|---|---|
| 加 / 减 | 非常便宜(1 cycle 左右) |
| 乘法(平方就是乘法) | 很便宜(1--3 cycles) |
| 除法 | 比乘法贵很多 |
sqrt |
明显更贵 |
在计算机图形学和 GPU 编程中,Warp(线程束)是 GPU 执行线程的一种调度单位。简单来说:
Warp = 一组同时执行同一条指令的线程集合。
最常见的情况是在 NVIDIA GPU 架构中:
-
1 个 Warp = 32 个线程
-
这 32 个线程会 锁步(lockstep)执行同一条指令
-
Warp 是 GPU 调度器分配执行的最小单位
每个线程的数据不同 指令相同?
1 什么叫 指令相同
假设有一段 Shader / GPU 代码:
color = textureColor * light;
GPU 在某一时刻执行的 指令 其实是类似:
MUL r0, r1, r2
意思是:
r0 = r1 * r2
在一个 Warp(32个线程) 中:
所有线程此刻都在执行这一条 MUL 指令。
所以叫:
Single Instruction(单条指令)
2 什么叫 数据不同
虽然执行的指令一样,但每个线程操作的数据不同。
例如在 Pixel Shader 里:
屏幕有很多像素:
pixel0
pixel1
pixel2
pixel3
...
Warp 中:
thread0 → pixel0
thread1 → pixel1
thread2 → pixel2
thread3 → pixel3
...
当执行:
color = textureColor * light
实际上发生的是:
thread0: color0 = texture0 * light0
thread1: color1 = texture1 * light1
thread2: color2 = texture2 * light2
thread3: color3 = texture3 * light3