目标
火焰图纵向堆叠的是调用关系,横向宽度才是成本:最宽的那一帧吃掉了最多的采样。按宽度优先阅读,直接回答“CPU 到底花在了哪”,不需要任何人的理论。配合负载下抓取与修复后复测,一次性能调查能以“某函数占比从 31% 降到 2.4%“这样可写进 PR 描述的数字收尾。
步骤要点
- 用 –cpu-prof 启动进程并用负载发生器复现生产流量形态,结束后在输出目录得到 .cpuprofile 文件。
- 先用一段 Node 脚本解析 profile 输出 hitCount 排名前 10 的函数,通常有一个函数的采样数数倍于第二名。
- 用 0x 或 Chrome DevTools 打开火焰图,从 main 沿最宽塔向下读,记下通往热点函数的完整调用链与每一层占比。
- 修最宽的帧而不是名字眼熟的帧:把每行调用 4 次的 escapeHtml 改为提升查表与正则、并在渲染入口对每字段只转义一次。
- 相同负载再抓一次 profile,用脚本对比修复函数的采样占比,把前后数字写进 PR 描述并留作下次基线。
排错提示
profile 几乎为空是负载窗口太短或进程空闲,让采集横跨真实流量突发并确认 hitCount 非零;处处都宽没有主峰说明成本摊在框架分发层,提高采样分辨率、看模块级聚合,或从缓存与减少行数这类架构层优化入手;担心采样开销就只对十分之一的副本开采样再外推,生产流量完全不受影响。
适用边界
采样只说明时间去向不解释原因,最宽帧仍需读代码才能找到可修的低效点;单次采集只代表一种流量形态,大版本上线后热点会迁移,需要重新抓取。
代码示例
main ................................. 100%
├─ server.dispatch .................. 88%
│ ├─ renderSearch ................. 61%
│ │ ├─ escapeHtml ............... 31% <-- widest leaf: the fix
│ │ ├─ deepClone ................ 12%
│ │ └─ JSON.stringify ............ 8%
│ └─ auth.middleware .............. 9%
└─ gc ................................ 4%
after hoisting the escape map + escaping once per field:
├─ renderSearch ..................... 38%
│ ├─ escapeHtml .................... 2.4%