关键在于提升缓存命中率:按行主序遍历、使用连续内存布局(如vector)、soa结构、对齐避免伪共享、合理预取。实测可提速2–8倍。
让海量数值迭代速度翻倍,关键不在算法复杂度,而在数据是否“顺手”——也就是内层循环能否持续从缓存中取数,而不是反复跑内存搬数据。现代cpu访问l1缓存只要1–4个周期,而访问主存动辄200+周期;一次未命中,可能拖慢整个循环十几倍。实战中,真正起效的不是调参数,而是对齐硬件行为:内存布局、访问顺序、缓存行边界。
按内存物理顺序遍历,别跳着读
C++/C数组是行主序(row-major)连续存储的。假设你有 int a[1024][1024],a[0][0] 到 a[0][1023] 在内存里挨着,但 a[1][0] 紧跟在 a[0][1023] 后面,不是紧挨着 a[0][0]。
- ✅ 正确:外层 i,内层 j → a[i][j] 是顺序读,每次加载一个 cache line(通常64字节),能预取后续7–15个 int(取决于 int 大小),命中率轻松超90%
- ❌ 错误:外层 j,内层 i → a[j][i] 是跨行读,每步间隔 1024×4 = 4KB,几乎每次都是 cache miss,实测慢 5–8 倍
把数据塞进连续块,别用指针满天飞
链表、vector
- 用 std::vector
替代 list 或自定义节点链表;T 尽量是 POD 类型(如 float、int、struct Point{x,y,z}) - 避免 “数组-of-pointers”,改用 “pointer-to-arrays”:比如处理 N 个三维点,用 vector
xyz(3*N) 存 x₀,x₁,…,y₀,y₁,…,z₀,z₁…,再用索引算偏移;比 vector快 2–3 倍 - 若必须分组,用 SoA(Structure of Arrays)而非 AoS(Array of Structures):例如粒子模拟中,把所有 x[]、y[]、z[] 分开存,比 struct Particle {x,y,z} 的数组更易向量化且缓存友好
对齐关键变量,避开伪共享(False Sharing)
多线程写不同变量,但如果它们落在同一个 64 字节 cache line 里,就会互相踢出缓存——哪怕逻辑上毫无关系。这是内层循环并发加速失败的常见原因。
- 用 alignas(64) 强制结构体或变量独占 cache line,尤其适用于线程私有计数器、累加器
- 示例:struct alignas(64) ThreadLocalAcc { double sum = 0.0; char pad[64 - sizeof(double)]; };
- 用 perf stat -e cache-references,cache-misses,cpu-cycles 检查:若 cache-misses / cache-references > 5%,且多核负载不均,大概率存在伪共享
手动提示预取(Prefetch),帮CPU抢跑一步
编译器不一定能准确预测你的访问模式,尤其在 stride > 1 或间接寻址时。可显式插入预取指令,把下一批数据提前拉进 L1/L2。
- 用 __builtin_prefetch(&a[i+8], 0, 3)(GCC/Clang),提前加载未来 8 步后的元素
- 适合场景:固定步长遍历、二分查找中间值、稀疏矩阵 CRS 格式中的列索引跳转
- 注意:过度预取会挤占带宽,建议先 perf 验证 cache-misses 是否下降,再决定是否启用











