三、性能分析工具(一):系统自带工具入门

做性能优化,第一步永远是「测量」。没有数据,一切优化都是瞎猜。

我见过太多开发同学,一上来就凭感觉改代码。改完一看,启动时间反而更长了。说白了,你都不知道瓶颈在哪,怎么改?

这章咱们就聊聊三端最核心的系统自带工具:Instruments(iOS)、Systrace/Perfetto(Android)、Chrome DevTools(Web)。

核心认知:这三个工具覆盖了90%以上的启动性能问题定位场景。学会它们,你就有了「眼睛」。

3.1 宏观视角:三端工具全景图

先上一张图,帮大家建立整体认知。这不是什么高大上的设计图,就是我自己梳理的工具使用脉络。

启动性能分析工具全景图 iOS 工具链 Instruments Time Profiler System Trace Dynamic Stack 关键指标 pre-main 耗时 + main 后首帧 Android 工具链 Systrace / Perfetto Ftrace events atrace 标签 SurfaceFlinger 关键指标 dispaly frame time + 主线程 wall time Web 工具链 Chrome DevTools Performance 面板 Coverage 面板 Lighthouse 报告 关键指标 FCP / LCP / TTI + 长任务 (Long Task) 核心原则:先测量 -> 找瓶颈 -> 再优化,不要凭感觉

你想想看,三个平台的工具虽然名字不同,但核心逻辑是相通的:记录时间线 -> 分析线程/进程行为 -> 定位耗时热点。掌握了这个通用逻辑,换平台只是换了个操作界面而已。

3.2 iOS:Instruments 入门

Instruments 是 Xcode 自带的性能分析工具。我个人习惯用 Time Profiler 和 System Trace 这两个模板来排查启动问题。

快速上手:分析 App 启动

  1. 打开 Xcode,连接真机设备(不建议用模拟器,数据不准)。
  2. 选择 Product > Profile,或按 Command + I。
  3. 在弹出的模板选择窗口中,选 Time Profiler。
  4. 点击录制的红色圆点,然后在设备上杀掉 App 重新打开。
  5. 等 App 完成首屏渲染后,停止录制。
小技巧:在 Time Profiler 中,把左下角的 "Call Tree" 选项里的 "Separate by Thread" 和 "Invert Call Tree" 都勾上。这样你会看到按线程分离的调用栈,而且最耗时的函数会排在最上面,清晰很多。

读数据:我重点关注两个阶段

  • pre-main 阶段:从点击图标到 main() 函数执行。这段时间主要花在 dyld 动态链接、ObjC 类注册、静态初始化器上。Instruments 里的 "App Launch" 模板可以直观看到这部分。
  • main() 之后到首帧:这个阶段主要看你的 UI 初始化、网络请求、数据库读取等逻辑。

我在项目中遇到过一个问题:某个版本的 App 启动突然慢了 500ms。用 Instruments 一抓,发现是某个第三方 SDK 的 +load 方法里做了大量计算。说白了,就是动态链接阶段被拖慢了。解决办法很简单,把那个 SDK 改成懒加载。

注意坑:Instruments 默认会收集所有线程的调用栈,数据量很大,偶尔会导致 App 卡顿。我曾经遇到过一次,录制的时候 App 本身都卡死了,记录自然没用。如果你也遇到这种情况,可以在 "Recording Options" 里把 "High Frequency" 关掉,或者限制采样频率(比如从 1ms 改成 10ms)。虽然精度降低一点,但至少数据是有效的。

3.3 Android:Systrace / Perfetto 入门

Android 这边,老项目可能还在用 Systrace,但 Google 已经明确推荐用 Perfetto 了。我个人建议直接用 Perfetto,功能更强,UI 也更现代。

快速上手:用 Perfetto 抓取启动 Trace

  1. 手机上安装 Perfetto 的客户端(或者直接用命令行)。
  2. 配置一个 Trace 配置文件,比如针对启动场景,我们关心主线程、RenderThread、SurfaceFlinger。
# 一个简单的 Perfetto 配置示例
buffers: {
  size_kb: 102400
}
data_sources: {
  config {
    name: "linux.ftrace"
    ftrace_config {
      ftrace_events: "sched/sched_switch"
      ftrace_events: "power/suspend_resume"
      ftrace_events: "binder/binder_transaction"
    }
  }
}

然后通过命令行启动录制:

adb shell perfetto -c /data/local/tmp/config.pbtxt -o /data/misc/perfetto-traces/trace.perfetto-trace

抓取完成后,把 trace 文件 pull 出来,传到 ui.perfetto.dev 上打开。

核心操作:在 Perfetto 的 UI 里,用鼠标拖出一个时间范围,按 M 键就可以标记。然后看底部的 "Critical Path" 分析,它会自动告诉你哪一段耗时最长。

我常用的分析技巧

  • 看颜色:主线程(通常是 tid=1)的状态通过颜色区分。绿色 = Running,蓝色 = Runnable,红色 = Sleeping/Blocked。如果红色特别多,说明线程在等锁或等 I/O。
  • 看 SurfaceFlinger:如果 App 启动过程中表面 Flinger 出现丢帧(jank),可能是 UI 线程耗时太长,导致掉帧。
  • 重点关注跨进程调用:Binder 调用在 trace 里有清晰的标识。我曾经遇到过一个问题:冷启动时主线程频繁调用 Binder 去获取系统服务,每次 wait 几十毫秒,累计下来就多了。

3.4 Web:Chrome DevTools 入门

Web 启动优化的工具相对容易上手,但很多人只用它看网络请求,忽略了 Performance 面板的强大能力。

快速上手:录制页面加载过程

  1. 打开 Chrome,按 F12 进入 DevTools。
  2. 切换到 Performance 面板。
  3. 点击左上角的录制按钮(或按 Ctrl+E)。
  4. 刷新页面(不用手动刷新,可以勾选 "Screenshots" 方便后面看画面)。
  5. 等待页面完全加载后,停止录制。

我怎么读 Performance 的火焰图

  • 看 "Main" 轨道:这是主线程的活动。如果看到一堆黄色的 "Task" 块,并且很长,说明有长任务阻塞了主线程。
  • 看 "Timings" 轨道:这里会显示 FP、FCP、LCP 等关键指标。我习惯先看 LCP,因为它是「用户感知加载完成」的最重要指标。
  • 看 "Network" 轨道:重点关注哪些资源的下载时间过长,或者有没有串行加载的请求。

我记得有一次,一个 H5 页面启动特别慢。用 Performance 一录,发现 LCP 之前的瀑布图里有一个 500k 的 JSON 数据请求。那个 JSON 是关键渲染路径上的资源,但它的服务器响应时间居然有 2 秒。说白了,后端接口慢,前端怎么优化都没用。我当时直接去找后端同学优化了接口,LCP 瞬间从 4 秒降到 1.5 秒。

一个很实用的工具:DevTools 的 "Coverage" 面板。打开它,录制页面加载,你可以看到哪些 CSS 和 JS 代码没有被用到。对于启动优化来说,把未使用的代码异步加载或删除,效果立竿见影。我在优化一个老项目时,靠它砍掉了 30% 的未使用 CSS,首屏渲染时间直接降了 200ms。

3.5 三端工具对比总结

为了方便你快速回忆,我把三端工具的核心对比整理成了表格。你可以在实际选型时参考。

对比维度 iOS (Instruments) Android (Perfetto) Web (Chrome DevTools)
最佳场景 启动耗时分析,线程调用堆栈 主线程卡顿分析,跨进程调用 资源加载分析,LCP/FCP 归因
核心数据 CPU 采样 + 系统追踪 Ftrace 事件 + 内核日志 JS 执行时间 + 渲染事件
上手难度 ★★★☆☆(需要熟悉 Xcode) ★★★☆☆(命令行门槛) ★★☆☆☆(UI 直观)
我最常用的场景 检查 +load 方法和动态库 分析 Binder 调用和锁竞争 查找长任务和未使用代码
最后提醒一下:这些工具收集的都是「运行时」的数据。你的手机越狱了吗?你的 App 用了热修复吗?你的页面开了 Service Worker 吗?这些都会影响性能数据。我建议你在「干净」的设备上测试,并且多次采样取中位数,别只抓一次就当结论。