性能指标定义:你得先知道量什么

我个人在做动态化性能监控时,有一个习惯:先把指标定义敲死。不然后面数据出来了,大家吵起来,谁都不认账。

说白了,指标就像一把尺。尺子刻度不对,量出来全是废数据。今天我们就把这几个核心指标摸透:冷启动、热启动、首帧时间、FPS、CPU/内存占用。

我遇到过不少团队,启动速度优化做了大半年,结果发现冷启动和热启动的定义就没对齐。前端说是冷启动,后端觉得是热启动。嗯,这就是典型的问题。

1. 冷启动耗时

冷启动,指的是进程从零开始,到用户看到第一个可用界面的全过程。没有缓存、没有预加载、没有热数据。

我个人习惯把冷启动分成三个阶段:

  • 进程初始化:应用进程创建,系统分配资源
  • 框架加载:JS引擎初始化、动态化容器创建
  • 首帧渲染:从数据请求到第一帧上屏

我在项目中遇到过很多冷启动的坑。比如,有个业务线的首页在冷启动时,光布局计算就花了800ms。你想想看,这800ms用户就在那干等。后来用Chrome DevTools的性能面板一查,发现是首次布局时,所有组件的样式规则都要重新解析一遍。

冷启动耗时标准

级别AndroidiOS
优秀< 2s< 1.5s
一般2s - 4s1.5s - 3s
较差> 4s> 3s

注意:冷启动必须包含网络请求时间!有次我看到上报的冷启动耗时只有1.2s,结果发现是数据还没请求完就上报了。这就是典型的定义问题。

2. 热启动耗时

热启动就简单多了。进程已经在后台存活,用户只是重新回到前台。说白了,就是从Application.onCreate之后到首帧渲染完成的时间。

我在监控热启动时,最关注的是两个东西:

  • 资源命中率:本地缓存是否还在,模板是否需要重新请求
  • 首帧复用率:上次渲染的视图是否可以直接复用

热启动理论上控制在200ms以内才算合格。我曾经遇到一个案例,热启动耗时780ms,排查发现是每次回到前台都要重新创建动态化容器。明明容器可以复用,代码里却写了销毁重建的逻辑。

这里有个小技巧:用adb shell dumpsys activity可以拿到进程状态。如果进程状态是STOPPED,那就是冷启动。如果STOPPED都没出现过,那就是热启动。

我个人习惯用打桩的方式在底层埋点。比如在动态化容器的onCreate和onViewCreated之间记录时间戳。这样能精确知道热启动的瓶颈在哪个环节。

3. 首帧时间

首帧时间,指的是从用户触发启动行为,到屏幕上出现第一帧画面的时间。这个指标最容易被人误解。

很多人觉得首帧时间等于页面渲染时间。不对。首帧时间只关心显示第一帧,不关心页面是否完整。哪怕只画了个背景色,那也是首帧。

我在项目中常用两种方式来测量首帧时间:

  1. 系统API法:iOS的CADisplayLink,Android的FrameMetrics
  2. 打桩法:在渲染管线的最前端和后端埋点

你想想看,首帧时间慢了,用户直观感受就是“点了一下没反应”。我曾经用500ms的首帧时间和1200ms的首帧时间做了A/B测试,用户留存率差了将近8%。

对了,首帧时间有一个坑:动态化场景下,首帧可能是容器本身的默认视图,不是业务内容。这意味着你看到的首帧时间很漂亮,但用户看到的是个白屏盒子。所以,我建议首帧时间从业务层面定义——真实内容的第一帧。

首帧时间测量说明

// Android 示例:使用 FrameMetrics
window.addOnFrameMetricsAvailableListener({ _, frameMetrics ->
    val firstFrame = frameMetrics.getMetric(FrameMetrics.FIRST_DRAW_FRAME)
    Log.d("FirstFrame", "首帧耗时: ${firstFrame / 1_000_000}ms")
}, Handler(Looper.getMainLooper()))

4. FPS(帧率)

FPS是一个老生常谈的指标,但我发现很多人只看平均值。平均值能骗过大多数人。你说你FPS是55,但中间有几帧卡了200ms,用户一样觉得卡。

我建议关注这三个维度:

  • 平均FPS:整体性能水平
  • 低帧率占比:FPS<30的帧数占总帧数的比例
  • 帧间隔抖动:相邻帧之间的时间差,这个最能反映卡顿感

动态化场景下,FPS下滑最常见的原因有两个:

  1. JS线程阻塞:动态化容器的渲染依赖JS线程,一旦JS卡住,FPS直接断崖
  2. 布局重计算:动态化布局每次都要从模板解析成原生视图,这个计算很贵

我曾经在项目里遇到过FPS一直卡在25左右的问题。排查后发现是模板中用了大量嵌套的Flexbox,每次布局都要递归计算。后来改成扁平化布局,FPS直接跳到55。

用Choreographer可以监听帧回调。我习惯在回调里记录frameTimeNanos,然后计算相邻回调的时间差。超过16.6ms就是掉帧了。

5. CPU / 内存占用

CPU和内存,是性能监控里最基础的两个指标。但很多人忽略了一个重点:瞬态峰值比平均值更致命。

比如冷启动时,CPU可能在短时间内冲到80%以上,然后回落到10%。如果你只看平均CPU用量,是看不出这个波动对系统的影响的。

我建议这样监控:

指标监控方式参考红线
CPU峰值每100ms采样一次< 80%
CPU均值启动开始到首帧后的1s< 30%
内存峰值启动过程中最大瞬时内存< 总内存的40%
内存泄漏连续5次GC后内存不回缩触发告警

动态化容器启动时,内存占用大多是模板解析和JS引擎的开销。我见过一个案例,模板文件有2MB,每次启动都要全部解析成AST树,光这一步内存就涨了150MB。后来改成懒加载+模板缓存,内存降到了30MB左右。

注意:不要把CPU和内存分开监控!它们之间有关联性。CPU飙升往往伴随内存分配加剧,这时候GC会被频繁触发,又反过来拖慢CPU。我曾经就因为只监控CPU,忽略了内存压力,结果定位到问题花了两周。

动态化启动性能指标定义与监控闭环 指标定义与监控目标 冷启动耗时 热启动耗时 首帧时间 FPS 帧率 CPU / 内存占用 综合性能评分与告警 指标监控闭环说明 指标定义 → 数据采集 → 异常告警 → 优化反馈 → 指标再定义 核心原则:指标必须可量化、可复现、可对比;避免模糊定义导致数据口径不一致。

我个人总结的三个原则

  1. 指标定义必须在团队内达成一致,文档化、版本化管理
  2. 冷热启动要分开看,混在一起等于没看
  3. FPS和内存是联动指标,出了问题往往是一起出