D1 miss rate高不等于局部性差,真实局部性差表现为D1 miss集中于小范围地址跳转或跨步访存;需结合cg_annotate看源码级D1mr分布,识别stride访问、false sharing等根本原因。

直接看 D1 miss rate 和访问模式分布,不是看“命中率”数字本身
为什么 D1 miss rate 高不等于局部性差
真实局部性差的表现是:D1 misses 占 D1 refs 比例高,且这些缺失集中在**小范围地址反复跳转**或**跨步访存**上。但 cachegrind 默认输出的 D1 miss rate(比如 1.6%)容易误导——它只是全局平均值,掩盖了局部热点。例如一个循环里 90% 的访存都在同一 cache line,另 10% 分散在千行之外,整体 miss rate 可能很低,但那 10% 就是局部性灾难。
关键要结合 cg_annotate 看源码级分布:
- 用
cg_annotate cg.out --auto=yes --show=I1,D1,LL查看每行的D1mr(一级数据缓存缺失数) - 如果某一行的
D1mr > 0且D1rd + D1wr很小(比如只有 1–2 次访问却 miss 1 次),说明该次访存大概率没复用任何已有 cache line,是随机/稀疏访问 - 对比两个相似循环:行号连续、访问
array[i][j]的循环,若j在外层,D1mr总和常高出 5–10 倍——这就是典型的列优先导致的 stride-100 访问,cache line 利用率趋近于 1/100
怎么识别 stride 访问这种隐性局部性差
缓存模拟对 stride 行为极度敏感,但 cachegrind 不直接标出 stride 值。你需要从访问计数反推:
- 编译时加
-g,确保cg_annotate能映射到具体数组下标表达式 - 找
D1mr集中、但D1rd分散在多行的函数——比如一个for (int i = 0; i < N; i += k)循环,k > 64 / sizeof(int)(即跨 cache line)时,每轮都触发新 miss - 注意内联干扰:
cachegrind把内联函数的访存计数归到调用点,不是原函数。若发现某调用行D1mr异常高,先用gcc -O2 -fno-inline重编译验证是否内联扭曲了归属
别信 LL miss rate —— 它根本不能反映局部性
LL misses 是末级缓存未命中总数,但它混入了所有 L1/L2 已命中的访存路径以外的流量,数值大只说明“有大量访存最终落到 LLC”,不区分是局部性差还是工作集太大。更危险的是:当程序实际运行在 L3 缓存显著大于 --LL 参数设定的机器上时,cachegrind 的 LL misses 会虚高,误判局部性。
真正该盯住的是 D1 层:
-
D1 misses/(D1 reads + D1 writes)> 5% 且无法归因于初始化/冷启动,基本可判定局部性差 - 如果
D1 misses中写 miss(D1wr对应的 miss)占比超 30%,大概率存在 false sharing 或非对齐写,这也是局部性被破坏的信号 - 用
--cachegrind-out-file=固定输出名,再配合grep -E "D1mr|D1rd|D1wr" cg.out | head -50快速扫高频缺失行,比等cg_annotate渲染更快
局部性不是“有没有”,而是“在哪崩”。cachegrind 给你的是缺失发生的位置和频次,不是原因;看到高 D1mr 后,必须回到源码看内存访问的 stride、对齐、复用距离——这些它永远不告诉你。


















