克魔助手
全部文章

在不是你挑的硬件上追掉帧

3 分钟阅读性能测试

“每秒 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 的读取看起来是免费的,直到它出现在一个每秒触发六十次的回调里。

做了马上就要扔掉的活

预取六屏之外的内容、给永远不会被看到的 cell 排版、为了三个字段解析整个响应。单独看都没错,但它们抢的是同一个 16.7 毫秒。

不会体现在 FPS 上的卡顿

有些体感最差的问题,根本不会让帧率曲线动一下:

  • 点下去 300 毫秒才有反应。那几帧都很正常,只是里面什么都没发生。
  • 动画起得晚。起来之后很流畅,但比手指抬起晚了一拍。
  • 转场掉了一帧。统计上看不见,体感上非常明显。

这几类要看交互前后的 CPU 曲线,并对照日志读。拖动过程中日志输出出现一段空白,往往是“确实有东西阻塞了”最清晰的证据。

让结论站得住的测法

  • 用你支持的最旧机型。 那里流畅,别处就都流畅。
  • 先让设备凉下来。 降频状态下的采集,量的是温度。
  • 重复同一段交互。 同一个列表、同样距离、同样速度。
  • 在数据线上采。 无线采样自带抖动,你会把它当成 bug。
  • 对比两次采集,而不是看一次。 改动前后各一次。单次采集只是一个数字,两次才是结果。

到哪儿为止

在一台三代前的设备上追最后那两帧,通常不是价值所在。目标不是一条平直的线,而是“用户高频操作的那些交互里,没有一次他能察觉的下陷”。把那些修掉,在你支持的最慢硬件上验证一遍,然后收工。