Valgrind的cachegrind不直接输出缓存命中率,仅提供I1/D1/LL的访问与缺失次数,需手动计算:D1命中率 = 1 − D1mr / (D1rd + D1wr);报告开头的汇总头(如“I1 misses: xxx”“D1 reads: yyy”)是关键数据源,应通过cg_annotate提取而非手动翻阅cg.out文件。

怎么看cachegrind输出的汇总头信息
cachegrind不直接输出“命中率”,只给I1 misses、D1 reads这类原始计数。你得自己算:D1命中率 = 1 − D1mr / (D1rd + D1wr)。报告开头那几行才是关键,比如:
I1 cache: 32768 B, 8-way, 64 B blocks D1 cache: 32768 B, 8-way, 64 B blocks LL cache: 8388608 B, 16-way, 64 B blocks I1 misses: 123456 D1 misses: 654321 LL misses: 98765 I1 reads: 2345678 D1 reads: 3456789 D1 writes: 456789 LL reads: 567890 LL writes: 678901
注意:LL misses不是L3命中率的分母——它只统计最终落到LL的未命中,漏掉了在L1/L2就命中的访存。所以别用LL misses / LL refs去算“L3命中率”,这数值没意义。
怎么提取cg.out里的有效数字,而不是手动翻文件
cachegrind输出的cg.out是混合格式:前面是汇总头,后面是逐行/逐函数的详细计数,中间还夹着注释。直接cat cg.out看容易漏或误读。
- 用
cg_annotate cg.out --auto=yes | head -20快速抓汇总头 - 查某函数总D1 miss数?用
cg_annotate cg.out --auto=yes | grep "function_name"(确保编译时加了-g) - 想导出纯数字做后续计算?用
cg_annotate cg.out --auto=yes --context=0 | sed -n '1,10p'避开源码行,只留数字行
别忘了--auto=yes会自动匹配符号,没这个参数可能显示为???。
为什么D1命中率比LL命中率更有参考价值
真实程序性能瓶颈通常卡在L1数据缓存(D1),因为L2/L3延迟高、带宽低,但它们只是“兜底”。D1 miss多了,CPU就得等,流水线停顿。而LL miss多,只说明最后一道防线也失守了,但问题根源往往早就在D1层暴露了。
- 如果
D1mr占比 > 5%,大概率有局部性差的问题(比如随机访问大数组、指针跳转频繁) - 如果
D1rd和D1wr悬殊很大(比如写远多于读),要检查是否无意中触发了写分配(write-allocate)导致额外读 -
I1 misses高?说明代码体积大或分支太多,指令预取跟不上,跟函数内联、hot/cold分离有关
LL数据更适合横向对比不同编译选项(如-O2 vs -O3)对末级缓存压力的影响,而不是诊断单次运行的瓶颈。
哪些情况会让cachegrind模拟结果严重偏离真实硬件
cachegrind是模拟器,不是采样器,它假设所有访存都按64字节块对齐、无bank冲突、无预取干扰。这些假设在以下场景会崩:
- 用了
_mm_stream_si32这类非临时存储指令:cachegrind仍当普通写处理,不会绕过cache,结果高估D1 miss - 大量非对齐访存(比如
char*指针+奇数偏移):真实CPU可能拆成两次访问,cachegrind只算一次 - 启用了
-flto且没加--read-var-info=yes:函数内联后,计数归到调用点而非原函数,源码关联失效 - 程序本身依赖CPU微架构特性(如Intel ADX、AVX-512掩码操作):cachegrind根本不识别这些指令,访存行为被忽略或误判
结论很实际:cachegrind适合找“明显糟糕”的缓存模式(比如遍历顺序反了、结构体填充太松),不适合抠±2%的命中率差异。真要精确,得上perf stat -e cache-misses,cache-references。


















