缓存局部性差导致CPU等待内存几十至上百周期,吞吐可跌至理论峰值10%以下;vector连续布局比list快5–10倍因空间局部性好,结构体字段重排和行优先循环顺序亦关键。

缓存局部性差会导致 CPU 等待内存几十甚至上百个周期
现代 CPU 的主频远高于内存带宽,L1 缓存访问约 1–4 个周期,而跨 NUMA 节点读取 DRAM 可能超 300 周期。一旦代码触发大量 cache miss,CPU 大部分时间在空等,而非执行指令。这不是“慢一点”,而是实际吞吐可能跌到理论峰值的 10% 以下。
用 std::vector 连续布局比 std::list 链表快 5–10 倍不是玄学
连续内存天然满足空间局部性:一次 cache line(通常 64 字节)加载,就能覆盖后续几次访问。而 std::list 每个节点动态分配,地址随机,每次访问都大概率触发新 cache miss。
-
std::vector中遍历 100 万个int:通常只需 ~16k 次cache line加载(假设 64 字节/行) -
std::list同样数量:每个节点至少 16 字节(含指针),但分散在堆上,cache line利用率常低于 20% - 结构体字段顺序也关键:
struct { bool a; double b; bool c; }会因填充浪费空间;调整为struct { bool a; bool c; double b; }可减少单次加载的冗余字节
循环嵌套顺序直接影响二维数组访问效率
C++ 数组按行优先(row-major)存储,arr[i][j] 中 j 变化最快时,地址连续;若写成 for (int j = 0; j ,则每次 i 变化都跳过整行,cache line 几乎无法复用。
int arr[1024][1024];
// ✅ 高效:内层沿连续地址步进
for (int i = 0; i < 1024; ++i)
for (int j = 0; j < 1024; ++j)
sum += arr[i][j];
// ❌ 低效:每次访问跨 1024×sizeof(int) ≈ 4KB,远超 L1 缓存容量
for (int j = 0; j < 1024; ++j)
for (int i = 0; i < 1024; ++i)
sum += arr[i][j];
手动预取(__builtin_prefetch)只在特定场景有用
编译器自动向量化或硬件预取已覆盖多数情况。强行插 __builtin_prefetch 容易适得其反:增加指令开销、污染缓存、干扰硬件预取逻辑。仅当有明确证据(如 perf record 显示 LLC-load-misses 集中在某段访存,且访问模式高度可预测)时才考虑。
- 预取距离要合理:太近无意义,太远可能被挤出缓存;常见是提前 128–256 个元素
- 只对读操作预取;写预取(
__builtin_prefetch(..., 1))需确认目标缓存策略支持 - 避免在分支内预取——预测失败时预取即白做,还占带宽
std::shared_ptr 管理对象生命周期很安全,但每个对象独立分配;换成对象池 + 索引(std::vector + uint32_t id)能提升缓存友好性,却要自己管生命周期和碎片。这类取舍没有银弹,只能靠 perf、valgrind --tool=cachegrind 实测定位热区。








