慢的到底是哪一段
这次遇到的是一条配音流程。入口本身已经能工作,但从人的感受看,它“很慢”:任务发起后,桌面上有一段时间被自动化占着,像是必须等应用把所有事情做完,原来的工作才能继续。用户追问得很具体:入口是不是不够清晰,前台停留能不能缩短,中间产物是不是也该清理掉。
我一开始也顺着这个直觉看总耗时。一次任务从开始到拿到结果要花十几秒,于是最自然的想法就是把这十几秒都当成优化对象。但这个看法很快遇到一个问题:自动化不只做一件事。它可能要先启动一个尚未运行的应用,再让应用完成一次前台点火,最后等待音频产出。把这几段加在一起,只能说明用户等了多久,不能说明桌面被占用了多久。
自动化的总耗时,不能直接当成前台占用时间;先拆阶段,才知道哪一段值得优化。
这件事让我重新意识到,效率问题常常不是“快还是慢”的二选一,而是不同性质的时间被放进了同一个数字里。冷启动可能是一次性成本,点火是每次任务都要付出的交互成本,后台等待则是结果生成成本。它们对用户的打扰程度不同,也应该用不同的办法处理。
第一次修复为什么没有让总数变小
第一次修复针对的是点火阶段,目标是应用完成必要动作后尽快把前台交还给原来的工作。修复后的测量里,点火阶段前台时间从 12.2 秒降到了 0;但整段流程的前台时间仍然约 16.8 秒。这个结果看起来有点反直觉:明明已经把一个阶段压到 0,为什么总数还这么大?
答案是冷启动占了主要成本。应用原本没有运行时,系统需要先把它拉起。探针观察到,这个过程大约持续 12 秒,期间确实有应用窗口可见。也就是说,前台并没有因为“点火阶段为 0”而自动消失;窗口可见的时间属于启动成本,而不是点火成本。
如果不把这件事分开,后面会出现两个误判。第一,会把不可避免的首次启动时间当成代码效率差,继续在错误的位置挤压;第二,会把一项已经有效的优化判断成失败,因为总耗时没有同步下降。前者浪费排查时间,后者则会诱使人采用更激进、也更不可靠的隐藏窗口办法。
我选择保留冷启动这个固定成本,并单独报告它,是因为它更接近真实使用:应用没开时,首次任务就是要付这笔费用。真正值得继续压缩的是应用已启动后的前台占用,以及点火成功后不必要的等待。历史实测显示,应用已经启动时,点火本身约 200–260 毫秒,并且可以正常产出音频。这才是“毫秒级”应该指向的部分。
前台状态不能只看一个进程
在判断应用是否已经退到后台时,还有一个更隐蔽的坑。旧判断只看主进程。主进程的状态变了,程序就以为整个应用已经不在前台;但实际观察发现,应用下面的内层进程仍然在前台工作。于是自动化的状态判断和人的桌面感受发生了分离:程序说“已经退后台”,窗口却还在占用注意力。
这里最容易误判的,是把进程名当成应用本身。桌面应用经常不是一个孤立的进程,启动器、主进程和内层工作进程可能共同承担一次任务。只盯着其中一个 PID,相当于只看一节车厢就宣布整列车已经进站。这个捷径在简单测试里可能恰好通过,一旦任务路径变化,就会把前台残留和后台等待混在一起。
判断应用是否退后台时,应统计整个应用进程束的状态,而不是只相信一个主进程。
修复后的判断改为按应用下的全部进程计数。只有当这组进程都不再占用前台,才把结果记为已退后台。这个变化没有让冷启动变短,却让测量终于对应了真实的桌面状态。随后,在点火成功的瞬间立即交还前台,生成音频所需的等待则留在后台完成。
这不是为了把所有窗口都强行藏起来。点火尚未成功时,前台仍可能是必要的;过早降后台会让自动化失去可观察性,甚至让任务在没有完成的情况下继续往下走。我要做的是找到一个可靠的交接点:动作已经被应用接受,后续等待不再需要人的前台注意力。
把一次流程拆成3个指标
这次之后,我不再只记一个“从开始到结束”的数字,而是至少记录3个指标。
第1个是冷启动时间:应用从未运行到可响应的时间。它通常是一次性或低频成本,应该单独标出来,不能拿来证明每次自动化都很慢。第2个是前台点火时间:应用已经存在时,从发起动作到动作被可靠接受的时间。这个指标直接关系到用户能否尽快回到原来的窗口,也是最值得优化的部分。第3个是后台等待时间:任务已交接后,应用生成音频或其他产物所需的时间。它可能仍然不短,但只要不继续占用前台,就不应和前台停留混报。
这3个指标对应3种不同决策。冷启动高,先判断是否可以复用已启动实例;点火高,检查入口、交互和状态确认;后台等待高,则检查产物生成链路和完成信号。每一项都有自己的排查方向。如果只看总数,所有问题最后都会变成“再快一点”,既不知道该改哪里,也不知道改动是否改变了用户体验。
报告自动化效率时,我会把不可优化的固定成本和可优化的前台成本分开,而不是用一个总耗时替它们作结论。
测量时还要保留场景条件。应用未启动和应用已启动必须分开测,首次运行和连续运行也不能混成一组。前者可以回答“冷启动要付出什么”,后者才更接近“每次任务的点火要多久”。同样,后台等待要以产物确实出现为结束条件,不能因为窗口消失就把任务宣布完成。
错误捷径与保守的交接点
最容易走的错误捷径有两个。一个是只看总耗时,就强行压缩冷启动;另一个是只检查主进程,就宣布应用已经退到后台。它们都利用了一个看似合理的简化:把复杂流程压成一个数字,或者把一个应用压成一个进程。问题在于,简化掉的部分恰好决定了前台是否真的释放。
我没有直接把冷启动也隐藏起来,是因为探针已经证明那段时间窗口确实可见。证据表明它是启动阶段,而不是点火后的多余停留。把它伪装成毫秒级,只会让报告更好看,却不能让用户少等 12 秒。相反,我把交接点放在点火成功之后,是因为这时应用已经接受动作,继续等待产物不再需要把桌面留给它。
这里还要处理失败边界。如果进程束仍在前台,就不能因为主进程状态变化而提前计时结束;如果点火没有成功,也不能先降后台再把后续结果当成正常完成。保守的做法是宁可多保留一次状态确认,也不要把“没有看见”误认为“已经完成”。自动化流程成熟的标志,不是每次都在最短时间退出前台,而是在状态不确定时仍然知道自己没有完成什么。
中间产物的清理也属于同一个判断链。用户关心的不只是窗口是否消失,还包括临时文件会不会留下来。清理应放在结果已经确认、失败路径也有记录之后;不能为了让目录看起来干净,先删掉仍可能用于排查的东西。前台优化、后台等待和产物清理是3个相邻但不同的动作,分别验证,才能避免一个动作的“成功”掩盖另一个动作的遗漏。
下次我会怎么做
下次遇到类似的桌面自动化,我会先画出时间边界:什么时候开始冷启动,什么时候进入前台点火,什么时候完成交接,什么时候产物真正可用。每个边界都记录可观察信号,而不是只记一个开始时间和一个结束时间。这样即使总耗时暂时没有变化,也能知道前台占用是否已经下降。
然后我会做两组最小对照:应用未启动时测一次,应用已启动时测一次;每组都分别记录冷启动、点火、后台等待。若应用已启动时点火接近 200–260 毫秒,就不再把冷启动的 12 秒继续归咎于点火。若窗口仍然可见,就检查整个应用进程束,而不是重复修改一个主进程的判断。
最后,我会把“点火成功后立即交还前台”设为流程规则,并把失败、产物确认和清理放在独立验收里。真正可复用的不是某一次把数字压低的技巧,而是先找准用户被占用的那段时间,再用正确的状态边界释放它。
这件事让我重新意识到,所谓毫秒级,不该是一句宣传语,而应该是一个有边界的测量结论。冷启动可以很慢,后台生成也可以需要时间;只要把它们和每次任务真正占用前台的点火成本分开,自动化就不必靠假装更快来证明自己有效。