Appearance
缓存友好与性能设计
性能优化应以 profiler、延迟分位数和真实负载为依据。算法复杂度、分配次数、缓存局部性、分支预测和并发争用往往比某一条语法技巧影响更大。
什么是缓存友好
CPU 通常按缓存行从内存取数据(许多机器为 64 字节,但不是 C++ 保证)。顺序访问连续数据能充分利用一条缓存行;随机指针追逐会增加 cache miss 和内存等待。
缓存友好代码的常见特征:
- 顺序遍历连续容器(如
std::vector); - 让经常一起访问的数据靠近;
- 减少每元素的动态分配和间接指针;
- 将高频访问的热数据与低频冷数据分离;
- 在并发写入时避免伪共享,详见“并发与多线程”。
AoS、SoA 与冷热分离
AoS(Array of Structures)适合每次都使用对象的多数成员:
cpp
struct Particle {
Vec3 position;
Vec3 velocity;
int id;
std::chrono::system_clock::time_point created_at;
};
std::vector<Particle> particles;如果物理更新循环只读取和写入 position/velocity,SoA(Structure of Arrays)可避免把 id 和时间戳反复载入缓存:
cpp
struct ParticleStorage {
std::vector<Vec3> positions;
std::vector<Vec3> velocities;
std::vector<int> ids; // 冷数据
std::vector<std::chrono::system_clock::time_point> created_at;
};SoA 不总是更好:当经常按单个实体访问全部字段,AoS 更直观也可能更快。选择取决于真实访问模式。alignas 可控制对象起始地址的对齐,但不能自动解决所有缓存问题;padding 也会增加内存占用,先测量再使用。
常见性能设计策略
- 先选对算法与数据结构:例如 Top K 常用大小为 K 的堆,而不是完整排序;
- 减少分配:已知规模时
reserve,复用缓冲区,避免热路径频繁构造大对象; - 批量处理:合并小 I/O、批量提交、按数组遍历;
- 控制间接层级:连续存储通常比链表和散落堆对象更利于缓存;
- 避免伪共享:不同线程频繁写的计数器可分开缓存行或先线程本地累积再汇总;
- 验证优化结果:在 release 构建、目标硬件和有代表性的输入下比较吞吐、P95/P99 与资源消耗。
性能面试的表达边界
std::vector扩容的增长倍率是实现细节,标准只保证相关复杂度;尾部追加通常是均摊 O(1);- 移动语义通常减少资源复制,但不要把“缓存命中率提升”当作它的必然直接效果;
- 虚函数、
std::function、RTTI 都有潜在间接调用/局部性成本,但不代表应在所有地方替换;先判断它们是否处于热点; - 多线程不必然更快:同步、调度、缓存一致性和下游饱和都可能使延迟变差。