← 中文资源

简体中文版本

用 CPU 火焰图定位 Node 热路径

负载下抓采样 profile,按宽度优先读火焰图找最宽帧,再用第二次抓取验证修复。

阅读英文原文

目标

火焰图纵向堆叠的是调用关系,横向宽度才是成本:最宽的那一帧吃掉了最多的采样。按宽度优先阅读,直接回答“CPU 到底花在了哪”,不需要任何人的理论。配合负载下抓取与修复后复测,一次性能调查能以“某函数占比从 31% 降到 2.4%“这样可写进 PR 描述的数字收尾。

步骤要点

  1. 用 –cpu-prof 启动进程并用负载发生器复现生产流量形态,结束后在输出目录得到 .cpuprofile 文件。
  2. 先用一段 Node 脚本解析 profile 输出 hitCount 排名前 10 的函数,通常有一个函数的采样数数倍于第二名。
  3. 用 0x 或 Chrome DevTools 打开火焰图,从 main 沿最宽塔向下读,记下通往热点函数的完整调用链与每一层占比。
  4. 修最宽的帧而不是名字眼熟的帧:把每行调用 4 次的 escapeHtml 改为提升查表与正则、并在渲染入口对每字段只转义一次。
  5. 相同负载再抓一次 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%