克魔助手
全部文章

在用户的真机上排查掉帧

4 分钟阅读性能测试

「每秒 60 帧」描述的是平均值,「卡顿」描述的是最差的那几帧。两者是不同的测量,而用户能感知到的只有后者。

帧预算是固定的

在 60 Hz 屏幕上,每帧预算为 16.7 毫秒;在 120 Hz ProMotion 屏幕上为 8.3 毫秒。这个预算不会因为芯片更快而变大,更快的芯片只意味着能在预算内完成更多工作。

因此,在最新机型上从未出现的卡顿,会在三年前的机型上稳定出现。旧设备本身没有问题,只是您的工作量超出了它的帧预算。

关注帧率下降

采集一段会卡顿的交互,按顺序完成三件事:

  1. 找到曲线下降的位置。 关注的是下降,而不是平均值。
  2. 记录下降发生的时机。 是启动时、第一次滑动时、每次加载图片时,还是页面转场时?
  3. 查看该时刻的 CPU、GPU 和内存。 诊断就在这一步完成。
下降时刻 说明
CPU 升高、GPU 平稳 主线程受限于 CPU
GPU 升高、CPU 平稳 受限于渲染
内存骤升 在帧中途进行了一次大的内存分配
网络有活动 在不应等待 I/O 的地方等待了 I/O
各项指标均无变化 阻塞在进程之外,或设备正在降频

结合四条曲线和时间点,多数情况可在一分钟内判断问题类型。

五个常见原因

解码原图

一张 4000 像素的照片放进 200 像素的 cell,仍会按 4000 像素解码。这会占用 CPU,并产生一次大的内存分配;如果发生在滑动过程中,就会导致掉帧。请在显示前先降采样,并在非主线程中执行。

在以列表为主的 App 中,这是滑动卡顿最常见的单一原因,也最容易修复。

随内容规模增长的布局计算

约束求解的开销并不随视图数量线性增长。如果每个 cell 都重建较深的视图层级,CPU 占用会随滑动速度上升:滑得越快越卡,这与通常的直觉相反。

阴影与模糊

这类效果在桌面 GPU 上开销很小,在手机上却很大。运动中的视图如果带有阴影但没有指定 shadowPath,系统每帧都要重新计算阴影形状。模糊的开销更大,它需要离屏渲染,叠加几层就足以使渲染成为瓶颈,即使您没有编写任何开销大的代码。

滑动回调中的同步操作

在拖动过程中访问磁盘、钥匙串或网络,都会明显影响帧率。UserDefaults 的读取看似没有开销,但放在每秒触发 60 次的回调中就不同了。

执行了随即被丢弃的工作

例如预取六屏之外的内容、为永远不会显示的 cell 排版、为了三个字段解析整个响应。单独看都没有问题,但它们都在争用同一个 16.7 毫秒。

不会体现在 FPS 上的卡顿

有些体验最差的问题,根本不会让帧率曲线发生变化:

  • 点击后 300 毫秒才有响应。这几帧都正常渲染,只是没有任何内容更新。
  • 动画启动过晚。动画本身流畅,但比手指抬起晚了一拍。
  • 转场时丢了一帧。统计上看不出来,体感上却很明显。

这类问题需要查看交互前后的 CPU 曲线,并对照日志分析。如果拖动过程中日志输出出现一段空白,通常是「确实有操作阻塞了」最清晰的证据。

让结论可靠的测量方法

  • 使用您支持的最旧机型。 在它上面流畅,在其他机型上也会流畅。
  • 先让设备冷却。 降频状态下采集到的数据反映的是温度。
  • 重复同一段交互。 同一个列表、相同的距离、相同的速度。
  • 使用数据线连接采集。 无线采集本身存在抖动,容易被误判为问题。
  • 对比两次采集,而不是只看一次。 改动前后各采集一次。单次采集只是一个数字,两次对比才是结果。

适可而止

在三代以前的设备上追回最后两帧,通常不值得投入。目标不是一条完全平直的曲线,而是「在用户高频使用的交互中,没有可察觉的帧率下降」。修复这些问题,在您支持的最慢设备上验证一遍,即可收尾。