内存布局:同样的算法,慢十倍往往是因为缓存
CPU 取一次内存要几百个周期,取整个缓存行只要一次。顺序访问与随机访问的差别往往大过算法阶数。
同一份逻辑,写成两个版本能差十倍。原因通常不在指令数,而在内存访问模式。CPU 一次从主存取 64 字节(一个缓存行),如果你只用其中 8 字节,剩下 56 字节就是白付的带宽。
顺序访问的价值
// 慢:外层遍历列
let sum = 0;
for (let c = 0; c < N; c++)
for (let r = 0; r < N; r++)
sum += matrix[r][c];
二维数组按行存储。上面这个写法每次跳一整行,几乎每步都命中新的缓存行。交换两个循环的顺序:
// 快:外层遍历行
for (let r = 0; r < N; r++)
for (let c = 0; c < N; c++)
sum += matrix[r][c];
同样的加法次数,顺序版每个缓存行服务 8 个元素(假设 8 字节元素)。N 大时差距可以是 5 到 10 倍。
数据结构的大小
// 每个元素 32 字节
type Node = { value: number; a: number; b: number; c: number };
// 每个元素 8 字节
const values = new Float64Array(n);
数组存 100 万个数字用 8 MB,指针数组存同样的数字用 32 MB 以上,且遍历时要追 100 万次指针。减少间接层本身就是优化。
结构体数组 vs 数组结构体
同一批数据,按字段拆成多个平行数组(SoA)通常比一个对象数组(AoS)快:
| 布局 | 适合 |
|---|---|
| AoS(对象数组) | 一次要读一个对象的所有字段 |
| SoA(平行数组) | 每次只读少数字段,或做向量化 |
只求总和时,SoA 不会把用不到的字段拉进缓存。
什么时候不必优化
- 数据量小到全在缓存里(几 KB)
- 访问本身不密集(一次请求读几十条)
- 瓶颈在网络或数据库
先测。 没有 profile 就说「缓存不友好」是在猜。真实的性能问题里,内存布局的收益经常小于减少一次网络往返。
现代 CPU 的算术极快,等待内存极慢。优化的第一性问题通常是「数据怎么排」,不是「算法几步」。

评论
…