外观
启动测试方法与诊断:场景、轮次、统计与提单
本文不重复 CoreProfile / kdebug 实现细节,而是回答测试人员最常遇到的问题:该测什么场景、测几轮、首轮怎么处理、哪些指标应该统计、看到慢启动后如何定位。
1. 先定义测试场景,再谈“快或慢”
Apple 将 warm 与 cold launch 描述为一个连续谱;真实用户会遇到不同缓存、内存压力和 prewarming 状态。为了让回归结果可比,测试报告必须先写明场景。
| 场景 | 定义 | 推荐用途 |
|---|---|---|
| Stable Process-Cold | 目标进程不存在,由 Perfowl 受控重新创建;不承诺系统缓存全冷 | 日常版本回归主场景 |
| First-Run | 新安装或清理业务状态后的第一次启动 | 一次性迁移、数据库初始化、首次配置 |
| Post-Boot / Colder State | 重启/更冷系统状态后首次目标启动 | 极端较冷条件观察 |
| Memory-Pressure Launch | 先制造较大内存压力再拉起目标 | page-in / dependency reload 压力场景 |
| Resume | 目标进程仍存活,从后台恢复 | 单独的恢复体验,不属于当前 Launch Total |
不要把这五种场景混在一个 P50 里。
2. 测试环境控制
为了做版本间公平比较,至少固定:
- 同一设备和 iOS 版本;
- 同一种连接方式;
- 同一 App 安装/账号/页面前置条件;
- 低电量模式、热状态、后台重载等环境条件;
- 同一启动场景和自动化动作。
网络是否需要固定取决于首帧是否依赖在线数据。如果目标页面会把网络请求阻塞在首帧之前,网络就是启动场景的一部分;不能简单把所有网络影响都当“外部噪声”。
3. 首轮不要机械舍弃
旧式规程常写“连续 10 轮、第一轮全部丢弃”。这会把一个真实用户场景静默删除。
更推荐:
text
Round 1
→ 单独记录为 colder-state / first observation
Round 2...N
→ 在确认测试前置条件一致后
作为 stable process-cold 分布如果团队明确只关心稳定态版本回归,可以使用后续稳定轮作为主分布;但仍应保留首轮数据,而不是从证据资产中删除。
4. 推荐的多轮统计
仅报告“平均启动 1.6 秒”是不够的。建议至少给出:
性能分布
- First Round;
- P50 Launch Total;
- P90 Launch Total;
- Max / 最慢轮;
- 相比上一版本 P50 / P90 的回归比例。
数据质量分布
- Total available:有 Total 的轮数 / 总轮数;
- Clean Total Rate;
- Repaired Total Rate;
- Total Missing Rate;
- 9/9、≥6/9 等 Phase Coverage 分布;
- 出现
deviceLostEventMarkerCount > 0的轮数。
示例:
text
10 rounds stable process-cold
Total available 9/10
Clean Total 7/10
Repaired Total 2/10
Missing 1/10
P50(clean) 1.42 s
P90(all usable) 1.91 s
9/9 coverage 5/10
>=6/9 coverage 10/10这比把所有轮次混成一个平均值更能反映工具置信度和真实启动稳定性。
5. 阶段诊断的正确顺序
启动慢时建议按以下顺序:
text
Launch Total 是否回归?
↓
Total provenance 是否 clean?
↓
本轮 coverage 是否足够定位?
↓
找占比/绝对增长最大的真实阶段
↓
结合 CPU / IO / Thread / Instruments 深挖不要因为某个阶段 missing 就直接认定它是性能瓶颈;missing 描述的是观测质量,不等于耗时无限大。
6. 常见阶段与排查方向
| 现象 | 优先检查 | 注意事项 |
|---|---|---|
| System Interface 增长 | 动态库、可执行文件/Framework 数量、依赖变更 | Apple 建议减少不必要第三方 framework;Xcode 15+ 可评估 mergeable libraries |
| Static Runtime 增长 | ObjC +load、C/C++ constructor、静态初始化 | 将非必要工作延后 |
| didFinishLaunching 增长 | SDK、数据库、同步 I/O、主线程阻塞 | 生命周期回调在主线程执行,长任务会直接拖慢首帧 |
| Scene 阶段增长 | Window / Root VC、状态恢复、路由容器 | 先确认该阶段本轮确实 captured |
| Initial Frame 增长 | 首屏布局、同步图片解码、同步读取 | First Frame 之后的网络/业务准备不属于该阶段定义 |
Apple 参考:Reducing your app’s launch time
7. First Frame 与“用户真正可用”要分开
如果 App 第一帧只是 Skeleton / Loading,随后仍需 1 秒才能让用户点击核心内容,那么:
text
Launch Total可能已经结束,但:
text
User-Ready / Business-Ready仍未完成。
这不是 StartupTiming 算错,而是指标终点不同。需要业务源码配合时,可使用自定义 signpost、XCTest extended launch 或产品自己的业务就绪事件建立第二层指标;Perfowl 的无侵入 Launch Total 不应凭视觉猜测扩展终点。
8. 多轮阶段分析:允许整轮择优,禁止跨轮拼接
假设:
text
r1 = 7/9
r2 = 9/9
r3 = 8/9为了找阶段瓶颈,可以选 r2 作为“本组最完整诊断轮”。
但禁止:
text
r1 的 System/Static
+ r2 的 Scene
+ r3 的 Initial Frame
= 人工 9/9因为这条启动在物理世界中从未发生过。
9. 基线与提单:优先比较回归比例
固定的 800 ms / 1500 ms / 2000 ms 可以做团队初始参考,不应作为所有 App 的通用物理标准。
优先采用:
text
同设备 + 同 OS + 同场景 + 同测试脚本比较版本变化:
text
P50 regression = (newP50 - oldP50) / oldP50
P90 regression = (newP90 - oldP90) / oldP90如果团队已经冻结 SLA,例如“核心入口 process-cold P90 ≤ 1.8 s”,再把它作为正式门槛。
10. 推荐 Bug 模板
markdown
【性能缺陷】启动 P90 较上一版本回归 24%,主要增长位于 didFinishLaunching
- 场景:Stable process-cold
- 设备 / OS:固定测试设备 + 固定 iOS
- 版本:baseline A → candidate B
- 轮次:10 rounds
- Total:
- P50: 1.21 s → 1.39 s (+14.9%)
- P90: 1.45 s → 1.80 s (+24.1%)
- Clean Total: 8/10
- Repaired Total: 2/10
- Missing: 0/10
- 阶段:
- didFinishLaunching P50: 320 ms → 610 ms
- Initial Frame P50: 基本持平
- 诊断轮 coverage: 9/9
- 环境:低电量模式关闭,设备温度正常,前置场景一致
- 初步判断:增长集中在 didFinishLaunching 区间;需研发结合 Time Profiler / 代码变更继续定位
- 附件:Perfowl Session + Startup timeline + baseline/candidate 对比这个模板将性能回归和采集质量一起提交,避免研发拿到一个无法判断可信度的单值。