APOGEE: Adaptive Prefetching On GPUs for Energy Efficiency
APOGEE 核心方法
FOA 预取器:
一个 warp 里的线程同时暴露地址规律,预取器瞬间学会"步长",下次按公式批量预取。
学习(Training)
场景: 8 线程的 warp 执行 load A[tid]
| 线程 | 索引 | 地址 |
|---|---|---|
| tid0 | 0 | 0x1000 |
| tid1 | 1 | 0x1004 |
| tid2 | 2 | 0x1008 |
| tid3 | 3 | 0x100C |
硬件并行计算:
(0x1004 - 0x1000) / (1-0) = 4(0x1008 - 0x1004) / (2-1) = 4(0x100C - 0x1008) / (3-2) = 4
判定: 所有相邻对算出的 offset 都是 4 → 学会 FOA,offset = 4
写入预取表:
PC=0x253ad, Address=0x1000, Offset=4, Confidence=高, Distance=1
预取(Prefetching)
公式: 下次地址 = 上次地址 + offset × 总线程数
- 上次 tid0 地址:
0x1000 - 总线程数 = 8
- 下次 tid0 地址 =
0x1000 + 4×8 = 0x1020
整个 warp 下次要访问的地址:
- tid0:
0x1020, tid1:0x1024, tid2:0x1028... tid7:0x103C
合并同一缓存行后,发 2 个预取请求到内存。
动态距离调整(及时性校正)
预取表每项带 2-bit 状态机:
| 状态 | 含义 |
|---|---|
00 |
刚执行这条 load |
01 |
预取已发,数据还没回来 |
10 |
数据已回缓存 |
太慢: 状态 01 时,这条 load 又执行了(缓存未命中)→ Distance + 1(提前更多)
太早: 状态 10 时,这条 load 执行但缓存未命中(数据被挤掉了)→ Distance - 1(提前少一点)
刚好: 状态 10 时,缓存命中 → Distance 不变
TIA 预取器:
所有线程读同一个地址,预取器不需要算地址,只需要找一条"够早"的 load 当哨兵,提前把数据喊回来。
场景
循环体里:
PC0: load &LightColor ← 所有线程读地址 0xFACE(TIA)
PC1: load &Normal[tid] ← FOA
PC2: load &Position[tid] ← FOA
PC3: load &TexCoord[tid] ← FOA
...几百条指令...
回到 PC0
问题: PC0 第一次执行后,LightColor 在缓存里。但中间几百条指令把缓存挤爆,回到 PC0 时 0xFACE 已被逐出 → 又要等 400 周期去内存读。
学习
Warp 执行 PC0,预取器看到 8 个线程的地址全是 0xFACE。
offset = 0,所有线程一致 → 判定为 TIA。
预取表写入:
PF PC=PC0, Address=0xFACE, Offset=0, Load PC=PC1, Slow Bit=0
(Load PC 设为 PC0 之后最近执行过的 load,即 PC1)
预取与 LAL 链调整
第一轮:
- 执行到 PC1 时,预取器发现"我是 PC0 的哨兵"
- 发预取:
prefetch 0xFACE - 但 PC1 离 PC0 太近(只隔 50 周期),数据没回来 → PC0 未命中
动作: Slow Bit = 1,Load PC 前移一位 → PC2
第二轮:
- 执行到 PC2 时发预取
- PC2 离 PC0 有 150 周期,还是不够 → PC0 未命中
动作: Load PC 再前移 → PC3
第三轮:
- 执行到 PC3 时发预取
- PC3 离 PC0 有 500 周期 > 400 周期内存延迟
- 回到 PC0 时,数据已在缓存 → 命中!
动作: Load PC 固定为 PC3。以后每次执行 PC3,自动为 PC0 预取 0xFACE。
核心差异对比
| FOA | TIA | |
|---|---|---|
| 地址 | 每个线程不同,要算 | 所有线程相同,已知 |
| 难点 | 算对下次地址 | 找够早的触发时机 |
| 学习 | 算相邻线程 offset | 发现 offset = 0 |
| 预取 | 按公式批量算地址 | 用 LAL 链找哨兵提前触发 |
| 调优 | Distance 增减(状态机) | Load PC 前移(Slow Bit) |
评论