3. 启动性能测量与基准测试

智能座舱的启动性能优化,第一步不是改代码,而是把“启动到底慢在哪”测量清楚。Qualcomm 座舱平台通常运行 Android/Linux + Hypervisor 的多系统架构,从 BootROM 到用户可见桌面,跨越 Bootloader、Kernel、init、SystemServer、SurfaceFlinger、Launcher 以及 QNX 侧仪表等多个阶段。若没有统一的测量工具链和基准口径,优化很容易变成“凭感觉调参”。本节围绕 Bootchart、Perfetto、Systrace 三类工具,讲清它们各自适合回答什么问题、如何采集、如何标记关键路径,以及如何从 trace 中读出可行动的结论。

三类工具的定位与选择

Bootchart 是“启动阶段总览工具”。它通过采集 /proc 下的进程、CPU、磁盘统计,生成一张按时间轴展开的启动图,能快速看出 init 之后各服务的启动顺序、CPU 占用和 I/O 压力。它适合回答“整体启动分成了几段、哪一段最重”,但精度有限,不适合分析毫秒级卡顿。

Perfetto 是当前 Android 平台的主推工具,支持系统级 trace、进程内 trace、计数器、日志和内存快照,能同时覆盖 Kernel ftrace、atrace、HAL、SurfaceFlinger 和 App 自定义打点。它适合回答“某个阶段内部具体发生了什么、谁阻塞了谁”。Systrace 是 Perfetto 之前的经典工具,本质是 atrace + ftrace 的封装,输出 HTML 报告,适合快速查看 CPU 调度、频率、渲染和 binder 交互。在新平台上建议以 Perfetto 为主,Systrace 作为兼容手段。

座舱启动测量工具链与关键路径标记 启动阶段时间轴(示例口径) BootROM 0ms Bootloader ~800ms Kernel ~1.6s init/服务 ~2.8s SystemServer ~4.5s Launcher ~6.2s Bootchart 采集:/proc 进程、CPU、I/O 粒度:百毫秒级总览 回答:哪一段最重、服务顺序 局限:难定位毫秒级卡顿 Perfetto 采集:ftrace/atrace/自定义打点 粒度:微秒级系统级 trace 回答:谁阻塞谁、线程调度 优势:跨进程、跨系统统一视图 Systrace 采集:atrace + ftrace 粒度:毫秒级 HTML 报告 回答:CPU 频率、渲染、binder 定位:兼容旧流程、快速查看 关键路径标记(Trace Tag) BOOT_Start 系统上电起点 KERNEL_InitDone 内核初始化完成 SYS_ServerReady 系统服务就绪 UI_FirstFrame 首帧可见 分析闭环: 采集 trace → 对齐关键路径标记 → 计算阶段耗时 → 定位阻塞线程/资源 → 回归对比基线 基线口径:同一硬件、同一软件版本、冷启动 5 次取中位数,避免单次抖动误判

测量方法:从冷启动到基线口径

启动性能测量首先要固定口径。座舱场景通常区分冷启动、温启动和恢复启动:冷启动指完全断电后上电,温启动指系统重启但部分缓存仍在,恢复启动指从休眠唤醒。三者耗时差异可达数秒,混在一起比较没有意义。建议以冷启动为主口径,连续采集 5 次,去掉最大值和最小值后取中位数,同时记录每次的完整 trace 作为证据。

采集 Perfetto 的典型命令如下,示例中同时开启调度、频率、binder 和自定义 atrace 类别:

# 设备端采集 10 秒,覆盖完整启动过程
adb shell perfetto -o /data/misc/perfetto-traces/boot.pftrace -t 10s \
  -b 64mb \
  sched freq idle am wm gfx view binder_driver hal dalvik camera input res

# 拉取到本地
adb pull /data/misc/perfetto-traces/boot.pftrace ./boot.pftrace

# 使用 trace_processor 查询阶段耗时
trace_processor --httpd ./boot.pftrace

Bootchart 的采集更简单,在 Android 上通常通过 init 的 bootchart 服务或 bootchartd 生成 /data/bootchart/ 下的数据,再用 bootchart.jar 渲染成 PNG。它不需要长时间 trace,适合在版本迭代中做“粗筛”:如果某个服务在 Bootchart 上明显拉长了 init 阶段,再针对它开 Perfetto 细查。

关键路径标记:让 trace 说人话

原始 trace 里线程和函数名非常密集,必须靠关键路径标记把“启动里程碑”钉在时间轴上。推荐在 Bootloader、Kernel、init、SystemServer、Launcher 各阶段插入统一命名的 trace tag,例如 BOOT_Start、KERNEL_InitDone、SYS_ServerReady、UI_FirstFrame。在 Android 侧可用 Trace.beginSection() / Trace.endSection(),在 native 侧用 ATRACE_BEGIN / ATRACE_END,在 Kernel 侧可用 trace_marker 写入。

标记的价值在于把“启动总耗时”拆成可归因的区间。例如从 BOOT_Start 到 KERNEL_InitDone 是 Bootloader + Kernel 时间,从 KERNEL_InitDone 到 SYS_ServerReady 是 init 与服务启动时间,从 SYS_ServerReady 到 UI_FirstFrame 是 SurfaceFlinger 合成与 Launcher 首帧时间。每一段都能独立优化,也能独立回归。

数据采集与分析实战

假设某次冷启动 trace 显示:BOOT_Start 在 0ms,KERNEL_InitDone 在 1.6s,SYS_ServerReady 在 4.5s,UI_FirstFrame 在 6.2s。总耗时 6.2s,其中 init/SystemServer 段占了 2.9s,是最大头。接下来在 Perfetto 中放大 1.6s~4.5s 区间,观察 SystemServer 主线程的 slice 堆叠,常见可疑点包括:PackageManagerService 扫描应用耗时、ActivityManagerService 启动关键服务、以及 binder 调用等待。

如果发现 SystemServer 主线程在某个 binder 调用上等待超过 300ms,就继续追到对端进程,看是哪个服务响应慢。若对端是 HAL 或 QNX 侧,还要结合 Hypervisor 的 vCPU 调度和共享内存通道一起看。此时 Perfetto 的跨进程视图比 Systrace 更有优势,因为它能把 guest 与 host 的 trace 合并到同一时间轴。

分析时建议同时关注 CPU 频率和调度状态。座舱 SoC 在启动早期常因温控或策略限制跑在中低频,若关键线程长期处于 Runnable 而非 Running,说明 CPU 资源竞争或频率不足,而不是代码逻辑慢。这类问题改代码收效有限,应优先调整启动阶段的 CPU 调频策略和任务亲和性。

建议为每个软件版本保留一份“启动基线包”:包含 5 次冷启动的 Perfetto trace、Bootchart 图、关键路径耗时表和硬件/软件版本号。任何优化前后都用同一口径对比,避免把环境差异误判为优化收益。

结果判读与常见误区

判读 trace 时,先看关键路径标记之间的总耗时,再看区间内最长的 slice 和最多的阻塞点。不要一上来就盯着某个函数名,而要先确认它是否在关键路径上。很多耗时操作发生在非关键线程,对启动总时长没有贡献,优化它们不会缩短用户等待时间。

另一个常见误区是只看平均值。启动耗时分布往往有长尾,某一次因为存储抖动或服务超时导致 8s,平均值仍可能显示 6.3s。因此除了中位数,还应记录 P90 和最大值,并保留异常那次 trace 用于复盘。只有把测量口径、关键路径标记和分析方法固定下来,后续的启动优化才有可比较、可回归的基础。