callgrind的self cost仅统计函数自身指令执行开销,不含子调用;throw语句的self cost很低,但真实开销集中在__cxa_throw等系统函数的栈展开、类型匹配和析构操作中。

Callgrind self cost 是函数自身指令执行开销,不含子调用
self cost 在 callgrind_annotate 或 kcachegrind 输出中,表示该函数**不包含任何被它调用的子函数**所消耗的指令数(或模拟时钟周期)。它只统计函数体内部的指令执行量,比如循环、条件判断、变量赋值、算术运算等——但 不包括 你写的 fun2_throwError() 里 throw 指令触发的栈展开、异常对象构造、catch 匹配逻辑等底层开销(这些会被计入 __cxa_throw 等系统函数的 self cost)。
为什么 throw 的 self cost 往往很低,但实际很慢
看一个典型现象:fun2_throwError() 在 callgrind_annotate 输出里可能只有几条指令的 self cost,但整体耗时远超等价的状态码返回。这是因为:
-
throw本身编译成的汇编极少,self cost只反映这条指令的开销 - 真正的代价在运行时:栈回溯(unwinding)、
type_info查找、析构已构造对象、跳转到匹配的catch块——这些都发生在__cxa_throw、__gxx_personality_v0等编译器/ABI 提供的函数里 - 这些底层函数的
self cost才是异常处理的“真实成本”,而它们通常不在你的源码行号附近显示,容易被忽略 - 如果你没加
-g编译,callgrind可能无法把符号映射到具体函数,导致高开销分散在???行
如何看到异常处理的真实开销分布
只看 self cost 会严重低估异常代价。必须结合调用上下文和符号解析:
- 用
valgrind --tool=callgrind --dump-instr=yes --collect-jumps=yes ./your_program开启更细粒度采集 - 确保编译时带
-g -O0(调试信息 + 关闭优化),否则callgrind_annotate无法准确定位到__cxa_throw等函数的源位置 - 用
callgrind_annotate --auto=yes callgrind.out.XXX,让工具自动尝试解析系统符号(需安装valgrind对应的 debuginfo 包,如valgrind-dbg或debuginfo-install valgrind) - 重点观察
__cxa_throw、__cxa_begin_catch、std::terminate这些函数的self cost和called次数——它们才是异常性能瓶颈所在
真正影响性能的从来不是你写的 throw myException() 那一行,而是编译器和 libc++/libstdc++ 在背后跑的那几十个函数。别只盯着自己源码里的 self cost,要顺藤摸瓜找到调用链末端那些沉默但昂贵的系统级函数。











