先问一个问题

同样是 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;
};

用对齐把热点写入隔离开,通常能明显降低抖动。

少做指针追逐

链表、树这类结构在理论上优雅,但在缓存层面很吃亏。工程上更常见的折中是:

  • 用连续数组表示节点池
  • 索引代替裸指针
  • 批量遍历而不是随机跳转

实践建议

  1. 先用 profiler 看 cache miss,再改代码
  2. 热路径优先考虑连续内存
  3. 把“数据怎么放”当成接口设计的一部分

小结

性能优化不是玄学。对于 C++,缓存友好的数据布局往往比微观语法技巧更值回票价。