性能工程、剖析器与看见不可见
原文由 Nelson Elhage 于 发布,订阅该博客
最近有人向我推荐了 Gary Klein 和 Robert Hoffman 合著的论文“Seeing the Invisible: Perceptual-Cognitive Aspects of Expertise”。写得非常出色,有机会建议一读。
Klein 和 Hoffman 探讨了专家“看见不存在之物”的能力:除了观察环境中已有的显性数据和线索,专家还能感知这些线索背后的意涵,比如那些本应出现却缺席的“典型”信息、观测数据是否符合常态,以及基于某一时刻的快照或短暂观察来推断系统过去和未来可能的演变轨迹。
我想结合性能工程的具体语境,来聊聊这篇文章中的一些观点,以及剖析器能做什么、不能做什么。特别是,我认为这篇文章为我们提供了一个很好的视角,来解释为什么剖析器既是不可或缺的利器,却又远非性能工程的全部。
剖析器
我经常会遇到这样一种说法——有时是心照不宣的——认为给软件提速无非就是循环做这几件事:
- 运行剖析器
- 在剖析结果中找出最耗时的部分
- 把它们变快
然而在实践中,无论是我自己还是他人,按这个流程照做,很快就会遇到收益递减。软件依然比我们直觉上认为它“应该”或“本可以”达到的速度要慢得多(或者说,远未达到我们的预期),但接下来该如何取得突破,却变得无从下手。
我认为造成这种脱节的原因有很多,完全可以就此写上十几篇文章来探讨,不过结合上文提到的那篇文章,我想着重讨论这样一个观点:
剖析器只能向你展示“已有的东西”;而高水平的性能工程,则要求你去理解那些塑造并决定了剖析结果、却并未直接呈现在剖析结果中的信息。
看见不可见
我这话是什么意思?剖析结果中哪些信息是“已有的”,哪些又是“没有的”?
剖析器展示的是“已有的东西”:它非常具体、直观地告诉你,程序某次执行的时间都花在了哪里,通常以函数或调用栈帧的形式呈现。这无疑是有用的信息。
但这还远远不够!有时你会偶然发现某个由明显缺陷导致的热点,但在更多情况下——尤其是程序已经经过一定程度优化之后——你需要额外的信息来决定该采取什么行动、尝试何种优化。仅仅知道时间花在了哪里是不够的;我们还需要判断哪些耗时是可以消除或减少的,以及为此需要付出多大的代价、承担多大的风险。
举个具体的例子:如果我发现程序 80% 的时间都花在哈希表查询上,这究竟意味着我应该去优化哈希表本身,还是应该重新设计以减少查询次数?剖析结果本身无法直接给出答案,但专家却能凭借自身的经验与背景知识,从同一份剖析结果中洞察到这类信息。
换个角度说,剖析结果告诉你的是程序在某个操作上花了多少时间,却不会告诉你如果优化了这个操作,它又会花多少时间。
有时某个操作之所以慢,是因为它代表了程序的“核心工作”,是难以轻易削减的本质开销;而有时它慢却是出于非本质的原因,甚至完全可以被移除。剖析器只会告诉你程序在某个操作上耗费了时间,却不会告诉你这个操作是否已经得到了充分优化,也不会告诉你这部分工作是否可以被移除或转移到别处。然而,专家在解读剖析结果时,却往往能得出这些问题的答案。
甚至剖析器所提供的分析“框架”本身,也未必是最适合用来提出这些问题的框架。剖析器往往按时间顺序组织信息,或按函数、调用栈帧进行分组;但在很多情况下,换一种组织方式会更有成效。例如:
有时,按 I/O 时间与 CPU 时间来分析耗时更为重要;程序可能会在两者之间交替执行(或以不同比例并行执行),而我们希望按不同类别的 I/O 或 CPU 时间来拆解耗时,这些类别未必能清晰地对应到调用栈帧上。
有时——算是对上述情况的进一步泛化——最有用的维度是“程序正在使用哪些硬件资源,各自占比如何?”具体取决于程序本身,这会打开一扇通往多种视角的大门:
- 微架构资源:CPU 执行单元、缓存空间、译码器带宽等
- GPU 与 CPU 之间,或 PCI、NVLink 总线的带宽
- 以太网带宽、本地磁盘带宽与 CPU 吞吐量之间的对比
- 等等,不一而足
有时,我们需要跳出物理调用栈的维度,换用另一种组织框架。
一个典型的例子是用 C 层面的剖析器去分析 Python 程序。这时你通常会发现,绝大部分运行时间都花在了
_PyEval_EvalFrame上。这么说倒也没错,但几乎毫无用处;你需要把剖析结果“提升”到 Python 层面的调用栈维度,才能获得有价值的洞见。(这是“操作代码的代码”这类自动化工具共有的问题,我过去也稍有讨论。)
与专业经验的交织
我认为 Klein 关于专业能力的文章为我们理解这一状况提供了一些有用的框架。
专家,尤其是特定应用、编程语言或框架领域的领域专家,往往能在查看剖析结果时就“看”到上述许多问题的答案,即便这些答案并未字面意义上呈现在剖析结果中。
他们能够“读出言外之意”,去提出甚至回答那些“正确”或“更好”的问题,而不是止步于剖析器字面所回答的问题。此外,更重要的是,当由于某种原因无法做到这一点时,专家能更快地意识到剖析器问错了问题,转而去寻找或构建另一种工具,而不是继续对着同一份剖析结果撞墙,指望它能凭空给出它本不包含的洞见。
我认为,这一视角也能解释我在相关讨论和人们对性能工程的普遍认知中所看到的一些脱节。如果你观察一位资深的性能工程师,确实会发现他们花大量时间查看剖析结果;由此很容易得出结论,认为这在某种意义上就是他们主要在做的事情。
然而,仔细观察就会发现,这些资深的性能工程师使用剖析器的方式与新手截然不同,也与外行的想象大相径庭。而这种使用方式,正源于他们能在剖析结果中看到不同的信息!当他们查看剖析结果时,他们并非只看字面上展示的信息,而是将其作为众多输入之一,用以完善和构建自己对程序的认知模型,不断更新和演进对系统的心智模型,并据此提出和验证各种假设——再通过进一步的优化和剖析运行(可能会换用不同的剖析器)来加以检验。
随机一篇博客
评论
登录后参与讨论