本文译自「How I Used Perfetto to Separate Real Startup Improvements From Plausible Ones」,原文链接levelup.gitconnected.com/how-i-used-...,由James Cullimore发布于2026年4月21日。

当我打开 Perfetto 时,应用的速度已经提升了。
问题就在这里。
启动优化工作最简单的情况是应用明显运行过载,基准测试结果惨不忍睹,几乎任何合理的清理都能带来一些提升。而更难的情况则出现在应用已经运行良好之后,此时下一轮的改动很容易被合理化。
我当时就处于这种情况。
我已经有了启动基准测试数据,也已经有了基准性能数据。一次全面的清理已经显著缩短了启动时间。图表看起来已经足够好了,我可以就此打住,讲述一个现代 Android 应用速度提升的精彩故事。
但我对剩余的启动路径并不完全信任,所以没有停止。
我可以测量它,但却无法总是解释它。有些工作似乎发生在"启动前后",却无法明确判断它究竟是阻塞了首次绘制、安全地延迟执行、重复执行,还是仅仅发生在不恰当的时间点。这种不确定性正是性能改进效果不佳的根源。你开始修改代码,并非因为证据确凿,而是因为代码的结构让某种理论听起来合情合理。
Perfetto 正是在这个时候出现的。
它并非救世主,也并非最大的胜利,而是我不再将看似合理的解释视为证据的转折点。
如果你读过之前那篇关于启动的文章,那么这篇文章是它的后续,而非替代品。第一篇文章是关于如何在现代 Compose 应用中找到一个有意义的启动优势,并用基准测试来验证它。而这篇文章则探讨了之后出现的一个更有趣的问题:
哪些工作真正值得保留?
这是对实际创业工作的后续跟进,而非起点
重要的背景是,Perfetto 并非与糟糕的基准进行比较。它是在前两个阶段已经完成实际工作之后推出的:
-
基准测试结果已经展现出可重复的成功
-
更广泛的清理工作已经大幅降低了创业成本
这一点至关重要,因为它改变了标准。
在创业初期,几乎任何合理的改进都会让人兴奋。但在创业后期,兴奋感就显得廉价了。你需要一种方法来区分"这看起来可行"和"这经得起衡量"。
这正是我在此重视 Perfetto 的真正原因。
如果你想了解这款应用是如何发展到如今的,请先阅读之前的文章:
这篇后续文章将探讨第一篇文章中略有提及的一个更具体的问题:当应用速度已经提升之后,追踪功能还能带来什么好处?
在 Perfetto 出现之前,基准测试结果已经相当不错
在跟踪流程之前,启动过程已经呈现出可观的趋势。
在最初验证过的流程树中,仅基线分析就节省了:
-
主流程:112 毫秒
-
设置流程:126 毫秒
两条启动路径合计节省了 238 毫秒。
随后,更广泛的清理流程生效,启动时间的绝对值发生了显著变化。相对于之前的验证状态,分析后的启动时间中位数又下降了 248 毫秒。
这已经是一个相当可观的结果了。本来可以就此止步,将数据整理成简洁的图表,然后就此结束。
但我仍然对剩余的启动路径不够信任,所以没有就此止步。
仍然存在一些地方,感觉这款应用在"启动前后"执行着一些工作,但却没有明确解释这些工作是否在关键路径上、是否被安全地推迟、是否重复执行,或者只是在不恰当的时机出现。
正是这种不确定性导致了后续工作的糟糕表现。
Perfetto 恰好出现在最容易被滥用的阶段
我认为性能优化文章很少提及这一点。
当应用已经运行良好时,跟踪功能最容易被滥用。
这时,你打开跟踪日志,看到某个服务提前启动、进行了网络检查或执行了回调序列,就很容易说服自己进行看似技术严谨、规范的"巧妙"生命周期重写。有时这确实是个好改动。有时,这只是制造新的不确定性的花哨手段。
这就是这里存在的风险。
在这个阶段,我的确做了一些看似合理但最终被放弃的改动。这并非流程的失败。这就是流程运作的方式。
真正有用的结果并非"Perfetto 向我展示了一个巨大的隐藏瓶颈"。而有用的结果是:
-
它展示了在第一次抽签之前哪些工作真正发生了
-
它验证了某些检查可以在第一次抽签之后安全地进行
-
它展示了何时某个服务端的想法仍然过于嘈杂或结论不明确,以至于无法保留
这比仅仅依靠图表所能展现的要成熟得多。它在幻灯片上可能不够吸引人,但在实际代码库中却实用得多。
跟踪标签本身很简单:
kotlin
trace("AppStartup") {
registerEarlyListeners()
resolveInitialState()
renderRootUi()
}
trace("RuntimeConnect") {
startLocalWatchers()
createRuntimeClient()
openRuntimeConnection()
}
trace("TransportConnect") {
bindInputChannel()
bindOutputChannel()
beginReceiveLoop()
}
这并非复杂的工具。它只是提供了足够的结构,让我们不再空泛地谈论"启动工作",而是开始询问在第一次抽签之前究竟是哪些工作真正完成了。
最佳的 Perfetto 驱动改进是微小、具体且不起眼的
最站得住脚的基于追踪的优化并非大规模的架构重写。
而是将一组主屏幕启动检查从可见的启动路径中移除:
-
初始文件监视器
-
初始网络验证
-
首次服务器 ping
这些检查仍然重要。它们只是不需要与首次绘制争用。
追踪到位后,问题就变得简单多了:
这些检查是否出于正当理由而阻止可见的启动,还是仅仅因为从未强制执行而提前执行?
答案是后者。
因此,改进之处在于让第一帧落地,报告已完全绘制,然后启用第一轮监控和连接工作。改进后的正常追踪显示,这些切片出现在它们应该在的位置:首次绘制之后,而不是之前。
该更改的伪代码版本如下:
kotlin
var postDrawWorkEnabled by remember { mutableStateOf(false) }
onFirstFrameRendered {
markStartupComplete()
postDrawWorkEnabled = true
}
onScreenVisible(postDrawWorkEnabled) {
updateConnectivityFlags()
if (postDrawWorkEnabled) {
probePeripheralState()
validateNetworkState()
}
}
when(postDrawWorkEnabled && networkIsAvailable) {
runInitialServerCheck()
}
这种优化不会在社交媒体上引起轰动。但它恰恰是那种经得起现实考验的优化。

Perfetto 最有价值的贡献在于拒绝工作
这对我来说才是真正的教训。
最终 Perfetto 指导的迭代带来的性能提升是正的,但比之前的阶段要小:
-
verify端总共节省了 142 毫秒 -
speed-profile端总共节省了 44.5 毫秒
如果只看这些数字,你可能会觉得 Perfetto 可有可无。但我认为这忽略了重点。
到这个阶段,应用已经运行得更加稳定了。剩下的工作不再是寻找新的突破,而是确保上一轮工作的有效性。
Perfetto 在这方面发挥了作用,它让我们能够做出以下判断:
-
这项更改明显地将工作从关键路径中移开,保留它
-
这项更改在不改变行为的前提下改进了归因,保留它
-
这项服务生命周期调整没有产生足够清晰的结果,撤销它
最后一类更改的一个例子是延迟运行时启动路径,它在理论上看起来合理:
kotlin
onStartupRouteReady {
postToNextUiTurn {
waitBrieflyForFirstFrameToSettle()
trace("DeferredRuntimeStartup") {
startBackgroundService()
requestMissingStorageAccessIfNeeded()
initializeFileWatcher()
connectRuntimeClient()
}
}
}
这类更改正是追踪功能发挥作用的地方。很容易就能想出一个简洁的解释来说明为什么将服务和客户端启动时间推迟会有帮助。但要证明由此产生的追踪结果更清晰、更稳定,并且值得增加生命周期的复杂性,就困难得多。在这种情况下,这个标准比想法听起来是否严谨更重要。
这就是为什么我不会把这个阶段描述为"Perfetto 让启动速度更快"。清理工作已经完成了更多类似的工作。基线分析也已经完成了更多类似的工作。
我会这样描述:
Perfetto 严格控制了剩余的启动工作,确保只有那些站得住脚的改动才能保留下来。
这听起来并不花哨,但这正是后期性能优化工作所需要的。后期工作很少需要惊天动地的壮举,而是要让系统更难被欺骗。
逐步比较比最终结果更有意思
最终的启动数据固然不错,但步骤顺序更重要。
步骤顺序表明,不同的工具以不同的方式发挥了作用:
-
基准配置文件带来了第一个清晰、可衡量的成果
-
清理工作带来了最大的绝对节省
-
Perfetto 带来的直接收益较小,但决策质量却大大提高
这正是我希望其他团队能够借鉴的部分。
人们往往会认为故事中最后一个工具的功劳最大。这就是为什么人们最终会把追踪过程当作优化本身。
事实并非如此。
追踪过程的作用在于提供足够的可见性,让你能够判断优化是否真的有必要。

这篇后续文章改变了我的想法
之前那篇关于创业的文章让我得出了一个总体结论:很多有意义的创业工作并非始于"创业优化"。
这篇后续文章又补充了第二个结论:
一旦取得重大进展,最难的部分往往是拒绝保留那些看似明智的改动。
因此,我现在不再把 Perfetto 看作一个"寻找瓶颈"的工具,而更像是后期绩效工作的"吐真剂"。
当然,在更早期阶段它仍然有用。但它的价值不在于揭示了什么惊人的秘密,而在于它提高了标准。
它迫使最后一轮的改动去回答更棘手的问题:
-
这些改动真的能把工作往后推进吗?
-
它优化了关键路径,还是仅仅重新排列了代码?
-
基准测试结果是否有所提升?
-
如果跟踪信息仍然不明确,我为什么要保留它?
这些问题让我避免保留更多无关紧要的更改,而这些更改实际上并没有直接保存以毫秒为单位的跟踪信息。这或许是对跟踪价值最诚实的描述,尤其是在那些显而易见的优势消失之后。
下次我会重复的步骤
如果我在另一个应用上再次进行这项工作,我会遵循以下步骤:
-
对真实的启动路径进行基准测试,而不仅仅是针对某个启动器。
-
验证基线配置文件,而不是想当然地认为它们会有帮助。
-
进行清理工作,使启动路径更加健康。
-
当下一个问题不再是"启动慢吗?"而是"为什么这里仍然会出现这种情况?"时,添加跟踪功能。
-
只保留那些能够显著改善启动时间或启动清晰度,并足以证明其复杂性的更改。
最后一点至关重要。
我认为并非每个性能更改都需要以毫秒级的提升来衡量。有些更改值得保留,因为它们使系统更容易理解,也更容易发现未来的回归问题。
但即便如此,我仍然希望它们能够真正发挥作用。
Perfetto 就是如此。
这并非因为它是创业初期最大的成功案例,而是因为它有助于使最终的创业故事更具说服力。
结语
我认为,性能优化案例往往过于简化这一部分。我们喜欢那种"某项技术胜出,一张图表证明其有效性,然后结论完美契合"的版本。
但这次的体验并非如此。
这次的体验更像是先获得显而易见的提升,然后发现最后阶段的性能优化工作与其说是寻找新的"奇迹",不如说是拒绝自欺欺人。拒绝因为某些改动听起来很高级就保留它们。拒绝将那些并未真正节省成本的追踪数据归功于自身。拒绝将"可能更好"等同于"经过测量和解释"。
这就是为什么我仍然认为 Perfetto 是值得的,即使它并非启动阶段节省成本的最大来源。
基线配置文件首先实现了干净编译的提升。清理工作完成了大部分繁重的工作。Perfetto 使最后一轮工作足够严格,让我能够信任最终保留下来的部分。
从长远来看,这或许才是更有价值的结果。
启动速度快固然好,但启动路径清晰易懂则更好。能够帮助你明确哪些改动是真正必要的流程,才是你在开发下一个应用时能够继续沿用的部分。
欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!
保护原创,请勿转载!