先问一个问题
同样是 O(n),为什么有的循环快很多?
答案常常不在算法复杂度,而在 CPU cache 命中率。现代 CPU 的瓶颈常常是内存访问,不是算术指令。
AoS 与 SoA
常见结构:
struct Particle {
float x, y, z;
float vx, vy, vz;
int alive;
};
std::vector<Particle> particles;
如果你只更新位置,实际上每次还会把速度和状态也加载进缓存。更好的方式是 SoA:
struct Particles {
std::vector<float> x, y, z;
std::vector<float> vx, vy, vz;
std::vector<int> alive;
};
这样 CPU 读取的数据更“纯”,预取更有效。
减少伪共享
多线程里,两个线程写不同变量也可能互相拖慢,因为它们落在同一 cache line。
struct alignas(64) Counter {
std::atomic<uint64_t> value;
};
用对齐把热点写入隔离开,通常能明显降低抖动。
少做指针追逐
链表、树这类结构在理论上优雅,但在缓存层面很吃亏。工程上更常见的折中是:
- 用连续数组表示节点池
- 索引代替裸指针
- 批量遍历而不是随机跳转
实践建议
- 先用 profiler 看 cache miss,再改代码
- 热路径优先考虑连续内存
- 把“数据怎么放”当成接口设计的一部分
小结
性能优化不是玄学。对于 C++,缓存友好的数据布局往往比微观语法技巧更值回票价。