cachegrind 不直接标识缓存抖动,仅提供间接线索:需结合高 d1mr(>10%)、d1mr ≈ llmr、微调 d1 路数后 d1mr 暴涨、cg_annotate 中高频小块访问及变量布局冲突等信号综合判断。

缓存抖动在 Cachegrind 里根本不会直接标出来
Valgrind 的 cachegrind 工具不识别“抖动”这个概念——它只统计访问、缺失、读写次数,没有时间维度,也没有重用距离(reuse distance)或访问频率变化率这类指标。所谓“抖动”,通常指同一缓存行被反复加载/驱逐(thrashing),比如多个热点数据映射到同一个 cache set,导致频繁冲突缺失。Cachegrind 能给你的是间接线索,不是诊断结论。
看 D1mr 和 LLmr 的比值是否异常高
真正要怀疑抖动,得盯住一级数据缓存的冲突行为。如果 D1mr(一级数据缓存缺失数)远高于 I1mr(指令缺失),且 LLmr(末级缓存缺失)也同步飙升,说明大量访存没能在 L1 停住,还穿透到了 L3,这是抖动的典型副作用。
-
D1mr / D1refs > 0.1(即缺失率超 10%)就值得警惕;超过 20% 很可能有 set 冲突或工作集溢出 - 对比
D1mr和LLmr:若LLmr ≈ D1mr,说明 L1 缺失基本没在 L2/L3 找回,L2 可能也被绕过或失效——这比单纯 L1 miss 更危险 - 运行时加
--D1=32768,4,64(降低路数)再测一次:如果D1mr暴涨,而真实 CPU 是 8-way,那大概率是你的数据布局触发了低路数下的冲突
用 cg_annotate 定位“高频小块”访问模式
抖动往往藏在循环体内对几个变量/数组的密集轮转访问中,而不是单次大块拷贝。用 cg_annotate 看函数级或行级统计时,重点找:
- 某几行代码的
Ir(指令数)不高,但D1mr却显著高于周围——说明这几行在反复踢掉自己刚载入的数据 - 同一缓存行(64 字节)内多个变量被交替访问,比如结构体里两个 bool 成员隔了 60 字节,又都被循环修改,就会共用一个 cache line,相互驱逐
- 数组访问步长不是 64 的倍数(如
arr[i*7]),导致每次访问都落在不同 cache line,但总工作集又刚好卡在某个 set 容量边缘
编译和运行时必须控制变量,否则结果不可比
抖动分析对环境极其敏感,稍有变动就会掩盖问题:
- 务必用
-g编译,否则cg_annotate行号错乱,你根本找不到是哪几行在捣鬼 - 禁用
-flto:链接时优化会抹掉符号,cachegrind 无法把计数归因到源码 - 固定缓存参数:
--I1=32768,8,64 --D1=32768,8,64 --LL=8388608,16,64,否则默认值随 Valgrind 版本变,两次 run 无法横向对比 - 避免
-O3下的自动向量化:SIMD 指令可能把原本连续的访存打散成多路并发,加剧冲突,而 cachegrind 对此模拟不准
抖动不是靠一个数字判定的,它是一组矛盾信号的组合:高 D1mr、低局部性、固定缓存参数下对微小布局改动敏感。别信“命中率 92% 就没事”——如果这 92% 全来自三行代码反复刷同一组 cache line,程序照样慢得像卡住。











