缓存行对齐可避免伪共享,但应仅对真正并发读写的字段做隔离,优先用填充而非alignas(64);vector优于list,需reserve()避免realloc;aos转soa提升simd和遍历效率;__builtin_prefetch需谨慎使用。

缓存行对齐能避免伪共享,但别盲目用 alignas(64)
现代 CPU 的缓存行通常是 64 字节,如果两个高频访问的变量落在同一缓存行里,即使它们属于不同线程,也会因写操作触发缓存行无效(false sharing),严重拖慢性能。但直接给每个结构体加 alignas(64) 往往浪费空间且无必要。
- 只对真正并发读写的独立字段做隔离,比如生产者/消费者计数器、原子标志位
- 优先用填充字段(如
char pad[64 - sizeof(std::atomic<int>)];</int>)替代全局对齐,更可控 -
alignas(64)会让整个对象按 64 字节对齐,若结构体本身小(如 16 字节),可能造成 48 字节空洞,批量分配时内存放大明显 - 验证是否真有伪共享:用
perf record -e cache-misses,cache-references对比前后,别靠猜测
数组优于链表,但别忽略 std::vector 的 capacity 增长策略
连续内存天然友好缓存预取,std::vector 是首选;而 std::list 每个节点分散堆上,每次跳转都可能 miss。
- 构造时尽量预估大小并调用
reserve(),避免多次 realloc + memcpy 导致数据迁移和局部性破坏 - 避免在 vector 中间插入/删除(
erase(iterator)),它会移动后续所有元素,破坏访问模式 - 若需频繁头部增删,考虑
std::deque—— 它分段连续,每段内部缓存友好,但跨段访问仍有代价 - 注意
capacity()可能远大于size(),遍历时只应迭代begin()..end(),别误用data()+capacity()
结构体拆分(AoS → SoA)对 SIMD 和遍历效率影响巨大
把“一个对象含多个字段”(Array of Structs)改成“多个同类型字段数组”(Struct of Arrays),能让循环中对同一字段的连续访问命中同一缓存行,也便于编译器向量化。
- 例如处理 10000 个点:
struct Point { float x,y,z; };→ 改为三个独立std::vector<float></float>分别存 x/y/z - SoA 对随机访问不友好(要同步索引三个数组),但对批处理(如物理引擎更新、图像像素计算)提升显著
- Clang/GCC 在 -O2 下对 SoA 循环更容易自动生成 AVX 指令;AoS 即使加
#pragma omp simd也常失败 - 可借助
std::span封装 SoA 接口,隐藏底层布局,避免业务代码裸写多数组索引
用 __builtin_prefetch 要谨慎,别在热点循环里滥用
手动预取(prefetch)理论上能提前加载数据进 L1/L2,但实际效果高度依赖访问模式和硬件代际,弄不好反而增加总线压力。
- 只在明确知道下一批数据地址、且当前处理与加载有足够时间差时使用,比如遍历大数组前 128–256 个元素处预取
- 避免在分支内或小循环(
- 参数选
0(读)或1(写),别用3(暂存),后者在多数 x86 CPU 上已弃用或无效 - ARM 上对应的是
__builtin_arm_prefetch,指令语义不同,跨平台代码必须条件编译
缓存友好不是堆砌技巧,而是持续观察访问模式、测量 cache miss rate、再针对性调整的过程。最常被忽略的是:改完之后没验证 —— 用 perf stat -e L1-dcache-loads,L1-dcache-load-misses 看真实数字,而不是相信“应该更快”。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











