callgrind.out.[pid] 文件中无时间列,真正代表函数耗时的是 instrs 列(含子调用的总指令数),self 列表示本函数自身指令数,二者共同反映开销。

callgrind.out.[pid] 文件里哪列代表函数耗时
Callgrind 默认统计的是指令执行次数,不是时间。它用「指令数」近似反映开销,所以 self 列(本函数自身执行的指令数)和 instrs 列(含子调用的总指令数)才是关键指标,不是 ms 或 time ——那些字段根本不存在。
真正耗时高的函数,通常对应 instrs 值最大、且 self 占比也高的项。如果某个函数 instrs 很大但 self 极小,说明它只是个“中转站”,热点实际在它调用的下游函数里。
-
--inclusive=yes必须加,否则instrs只显示本函数指令数,看不到总开销 - 编译时一定要带
-g,否则callgrind_annotate无法关联源码行号 - 默认输出按
self降序,但你更该看instrs列——它才是“包含全部子调用”的真实负载
callgrind_annotate 怎么快速定位 top 函数
生成报告后别从头扫:直接用 head -n 50 report.txt 看前 50 行,重点关注 instrs 最大的几项。注意函数名右侧的路径和行号,比如 my_func (main.cpp:42) 这种格式。
- 如果看到大量
???或in ??,说明没编译调试信息,重编:g++ -g -O2 -o app app.cpp - 如果多个函数共享同一行号(比如内联展开),
instrs会合并统计,此时得结合--separate-threads=no(默认)或开--separate-threads=yes看线程粒度 - 用
grep -A 5 "function_name" report.txt快速跳转某函数上下文,看它的调用者(called by)和被调者(calls)
为什么 gprof 和 callgrind 的 top 函数经常对不上
因为两者原理完全不同:gprof 统计的是采样到的 CPU 时间点(基于 ET_EXEC + pg 插桩),而 callgrind 是模拟 CPU 执行每条指令,统计的是指令数。I/O 等待、系统调用、锁阻塞这些不执行用户指令的场景,gprof 会记为“空等”,callgrind 则完全不计入 instrs。
- 如果你的函数大量调用
read()、pthread_mutex_lock()或usleep(),gprof 可能把它排第一,callgrind 却显示instrs=0或极低 - 反之,一个纯计算函数(比如矩阵乘法循环)在 callgrind 中
instrs会爆炸式增长,gprof 也可能低估——尤其当优化级别高导致内联后函数名消失 - 结论:想查 CPU 密集型瓶颈,优先信 callgrind 的
instrs;想查整体响应延迟,得补 perf 或 strace
多线程程序 callgrind 报告混乱怎么办
默认情况下,callgrind 会把所有线程的指令计数混在一起写进同一个 callgrind.out.[pid],导致 instrs 失真——比如主线程调用 1 次,工作线程调用 1000 次,但报告里只显示“总共 1001 次”,看不出分布。
- 加
--separate-threads=yes,会为每个线程生成独立计数,报告里出现TID: 1234标识 - 但注意:这会让输出文件体积暴涨,分析变慢;若只需粗略定位,先用默认模式跑一次,确认是否真有线程 skew
- 如果程序用
std::thread或pthread_create启动大量短命线程,建议加--collect-jumps=yes辅助看控制流跳跃热点
真实瓶颈往往藏在「看似简单」的函数里——比如一个被调用十万次的 getter,每次只做一次指针解引用,self 很低,但 instrs 累积起来可能超过主逻辑。别只盯着单次耗时高的函数。











