做性能优化这些年,我最大的感受是:很多人一听到“高性能计算”,脑子里第一个反应就是“核数够不够、主频高不高、GPU是不是顶配”。但真正跑过大规模程序的人都知道,大部分性能杀手根本不在算力上,而在访存。CPU占用率跑满,吞吐却死活上不去,perf一抓,cache miss高得离谱——这种情况我遇到过太多次了。
更值得注意的是,这个问题换了身衣服又出现在大模型推理里。vLLM 为什么那么强调优化大模型缓存命中率?因为 KV Cache 命中率高不高,直接决定推理服务的首字延迟和吞吐。高性能计算里的缓存优化,和 LLM 推理里的缓存优化,底层逻辑其实是同一个:怎么让访问速度快的存储层级多命中,怎么让慢速存储少拖后腿。
这篇文章就从我自己的调优实践出发,把高性能计算缓存优化这件事从 CPU 三级缓存一路讲到 vLLM 的 Prefix Cache,把所有“为什么这么做”都说清楚,最后附上我踩过的坑。适合正在做计算加速的开发者、跑推理服务的运维,以及所有被访存性能卡住的同学。
1. 缓存优化的本质:CPU三级缓存与KV Cache在解决同一个问题
1.1 命中率为什么是高性能计算的“隐形天花板”
先看一张存储层级的典型数据,不同架构有差异,但量级基本一致:
| 存储层级 | 容量 | 访问延迟 | 带宽特征 |
|---|---|---|---|
| L1 Cache | 32-64KB | 约1ns | 极高 |
| L2 Cache | 256KB-1MB | 约4-10ns | 高 |
| L3 Cache | 8-64MB | 约15-50ns | 中等 |
| DRAM | GB级 | 约80-120ns | 瓶颈所在 |
| SSD/HDD | TB级 | 微秒-毫秒级 | 极低 |
L1 和 DRAM 之间差了两位数的延迟。这个差距意味着什么?一个程序如果执行一亿次访存操作,L1 全命中大概 0.1 秒,DRAM 全缺失可能要 10 秒。CPU 再快,数据送不到寄存器里就是白搭。
很多数值计算程序表面上在拼命算浮点,实际干的事是“等数据”——等 DRAM 把数据搬到 Cache 里。这就是为什么有些并行程序开了 128 个核,加速比却只有 20 倍,瓶颈几乎总是访存。
1.2 时间局部性与空间局部性:最基础的两个抓手
缓存优化的理论根基就两个词:时间局部性和空间局部性。
- 时间局部性:同一份数据在短时间内被反复访问。比如内层循环里的累加变量、反复读取的权重矩阵,这类数据应该让它一直留在 Cache 里。
- 空间局部性:程序访问的内存地址是连续的。CPU 加载缓存时是以 cache line 为单位的(通常是 64 字节),一次加载 64 字节,如果你把 16 个 float 按顺序访问完,那就是一条缓存行覆盖了所有有效数据。
生活化一点理解:Cache 就是厨房里的操作台,DRAM 是冰箱。你想做菜,最理想的状态是操作台上已经摆好了接下来几步需要用的所有食材。如果每炒一个菜都要去开一次冰箱,时间全耗在路上了。
大模型的 KV Cache 本质上也是这个逻辑。自回归生成时,模型算过的历史 token 的 Key 和 Value 矩阵被存下来,后续每个新 token 都要和它们做注意力计算。这就是典型的时间局部性——同一批 K/V 会被反复使用。而 KV Cache 按顺序存储、按块管理,对应的是空间局部性。理解了这一点,你就明白为什么传统 HPC 和 LLM 推理的缓存优化是同一套思想在不同场景下的落地。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 传统HPC里真正拉高命中率的三个手段:数据布局、分块、内存系统调优
2.1 数据结构重排:从结构体数组到数组结构体
这是性价比最高的一步,经常一个改动就能看到几个数量级的变化。问题出在缓存行利用率上。
举个例子。假设你在做粒子模拟,每个粒子有位置和速度六个浮点:
c复制struct Particle {
float x, y, z;
float vx, vy, vz;
};
Particle particles[N];
