性能文化

原文:Performance Culture

在这篇文章中,我将讨论“性能文化”。性能是软件工程的关键支柱之一,也是很难做得正确的事情,有时候甚至很难去识别。一位著名的法官曾经说过:“当我看到时,我就知道。”之前我已经单独详细地谈到过性能文化,但两者的交集才是有意思的地方。这方面做得好的团队能在一开始就将性能贯彻到团队操作的几乎所有方面,并且能够主动提供迷人的用户体验来赢得竞争。没有简单的一刀切的方法来实现一个良好的性能文化,然而的确有一些最好的实践你可以遵循来播种到你的团队中。那,Let’s go!

介绍

那么,为什么重点关注性能呢?

有部分原因是我的背景。我曾经工作在操作系统、runtime、编译器等东西上,都是一些客户期望能够很快的东西。相对于之后来修补,在此类项目的一开始就将目标、测量和团队进度从一开始就结合起来容易得多。我也曾经在很多团队工作过,一些在这方面做的非常棒,一些做得很差,而大多数是处于两者之间。一个统一的真相是区分的因素一直都是文化。

也有部分原因是因为,不管是什么类型的软件,性能几乎总是比我们客户期望的糟。这是一个简单的物理定律:给定有限的时间,以及在大小、速度以及功能的妥协,不可能加快程序的所有方面。但我坚信,在一般的团队中,对发展严谨的性能文化的关注度会比较低。我曾经听到过很多次“性能对于我们不是最优先”这样的话,直到痛苦地中止之后才认识到没有性能,产品不可能成功。

还有部分原因是它对于所有在 DevDiv 的我们就是信仰,因为我们聚焦于 .NET core 的性能、ASP.NET 的可伸缩性、整合促进性能的特性到 C# 和库中、使 Visual Studio 更快等等。它特别是我的信仰,因为我曾经一直和自己在 Midori 中的经历比较(给这篇帖子带来了最多的启发)。

诊断和治疗

你如何辨别你的性能文化是在正轨上呢?这里有一些迹象表示它们不是的:

  • 对“产品在我的关键性能指标上的执行情况怎么样?”这样的问题很难回答。
  • 性能经常退步,而团队成员不是不知道、不关心,就是反应得太慢。
  • 指责是对性能问题的最常见响应之一(对人员、基础结构,或者两者都是)。
  • 性能测试狂野地摆动,不能被信任,因而被团队中的大多数人忽略。
  • 性能是某些人或者某个人才会关注的东西,而不是整个团队。
  • 产品中的性能问题很常见,需要进行丑陋乱糟糟的处理(和/或不能被重现)。

这看起来好像是技术问题。然而,你可能会惊讶,其实它们主要是人的问题。

解决方案并不容易,特别是当你的文化杂草丛生时。一开始就不要挖坑,总比后面再爬出来容易。但第一条规则是,当你已经在坑里,立刻停止继续挖!文化的转型必须从上层开始 —— 管理层在性能中积极参与,提出问题,寻求洞察,要求严谨 —— 同时它也来自下层 —— 工程师积极寻求了解他们正在编写的代码的性能,严酷地对退步采取零容忍的立场,并不断自我批评,寻找积极的改善。

除了我发现的一些一旦你拥有它就对效果很有帮助的最佳实践之外,本文将介绍一些方法来确保这种文化。它们中很多看起来很显而易见,但请相信我,很少能见到这里的这些东西在实践中和谐地生效。但一旦它可以生效,哇!那将会是多么的不同!

对开源软件的简短说明:我是从商业软件开发的角度来写本文的。因此,你会比较多地看到“管理层”这词。许多同样的原则也在开源中起作用。所以,如果你喜欢,任何时候都可以将你看到的“管理层”在脑中转变成“管理人员或项目贡献者”。

自此至终,文化随行

一个健康的性能文化的组成部分包括:

  1. 性能是团队日常对话和“嗡嗡声”的一部分。每个人都参与了进来。
  2. 管理层必须关心 —— 真正的,而不是肤浅的 —— 良好的性能,并理解它需要什么?
  3. 工程师们对性能采取科学的、数据驱动的、刨根问底的方法。(测量,测量,测量!)
  4. 鲁棒的工程系统处于恰当的位置,在追踪目标中,在过去和现在的性能中,并防止退步。

我会花一点时间来粗略地讨论这些问题。

对话、嗡嗡声和交流

整个团队需要挂钩到性能上。

在我看到的很多入了歧途的团队中,一个人被冠以去关注性能的家伙/姑娘。现在,那样做很好,可以帮助团队测量,当某些人需要带头调查时或大力提倡性能是很好时很有用,但这绝不能付出团队的其他人不牵涉进来的代价。

这会导致跟微软之前有的“测试”制度类似的问题:工程师学到了将他们代码的基本质量外包,期望着某个其他人会发现出现的问题的坏习惯。通常的风险也会出现,当有一个中心的性能沙皇时:团队中的工程师不会写代码测试,不会主动进行基准测试,不会分析,不会问关于产品有竞争力的地位的问题,也一般都不会干所有你需要所有的工程师做的能够建立一个健康的性能文化的事情。

当整个团队都痴迷于性能时,就会发生魔术般的事情。由于挑战和改善的消息是有机地传播的,走廊中充斥着兴奋。“你看到 Martin 的减少进程占用空间 30% 的哈希表再平衡改动了吗?”“Jared 刚刚签入了一个让你可以在栈上分配数组的特性。我打算在这个周末研究下网络栈来用上它 —— 有兴趣加入吗?”即兴白板会议、即兴构思、小组学习。看到这些真的很棒!激情和胜利的渴望自然地推动团队前进,不需依赖某些重量级的管理“大棒”。

我讨厌指责,我讨厌防备。我的第一原则是“不要蠢货(no jerks)”,所以自然地所有的批量都必须以最有建设性和尊重的方式来传达。然而,在性能上表现不佳的团队,我发现经常的责备、防备和知识不诚实( intellectual dishonesty)。跟蠢货一样,这些都对团队的文化的毒害,必须大力清除。你发展正确的性能文化的能力很容易要么成功,要么失败。说我们需要在某些关键指标上做得更好,没有错,特别是如果你对如何做到这一点有好的想法!

在临时的沟通之外,当然也需要有结构化的沟通。稍后我会描述一些技巧。但,让一个核心组的人在一个房间里定期讨论对于产品特定领取过去、现在和未来的性能是必不可少的。虽然有机的对话是强大的,但每个人都很忙,所以安排时间作为提醒来推进很重要。

管理层:更多胡萝卜,更少棍子

每一个拥有很差性能文化的团队,都是管理层的错。句号。谈话结束。

当然,工程师们可以,也必须有所作为,但如果最上层的人和中层的每个人没有深度参与,安排必要的时间,并奖励工作和超级明星,那么正确的文化就不会出现。单独的工程师不可能将这种文化注入到整个团队中,整个努力都是逆流管理层而上是当然不行的。

看着不欣赏性能文化的管理层是痛苦的。他们经常被意外地扯后腿却不知道为什么 —— 或者更坏,认为这只是工程师需要坐的。(“我们不能预先预测性能在什么地方凑效!”)客户会抱怨产品不会在一些关键的领域表现得符合预期的效果,并且意识到预防措施太晚了,一个有糟糕性能文化的团队的管理人员会开始指责。猜下会发生什么?指责游戏像野火一样蔓延,工程师开始也这样做,问责制也就消失了。指责不会解决任何问题。指责是蠢货才会做的。

请注意,我说管理层必须“深入参与”:而不是一些肤浅的参与。当然,绿的红的趋势图可能需要在周围浮动,定期的检查也很重要。我想你会说这些是没几根头发的管理人员(pointy-haired manager)的事情。(相信我,即使如此他们也有帮助。)然而,管理人员必须做得更进一步,主动和定期地检查整个产品的性能状态,以及其他基本质量指标和特性方面的进展。这是团队工作的核心信条,必须这样对待。管理人员必须对竞争环境进行考量,并向团队提出好的有见地的问题,让他们思考。

性能不会从天下掉下来。它需要开销,强迫团队有时候放慢速度,把精力花在其他地方而不是赶功能,并因此需要一些聪明的权衡。到底有多少东西取决于这个场景?管理人员需要直到团队花费正确的时间比例。那些以为性能是免费的人最终会花费 2 至 5 倍多的它本应该花的成本,就在之后不适当的时间点(例如,在产品出货的最后阶段,在生产环境,试图从 1000 个客户扩展到 10 万,等等)。

我的一位导师曾经说:“你奖励了什么,你就会从团队中得到什么。”这对于性能和围绕着它们的工程系统特别正确。考虑两个管理人员:

  • 经理 A 对性能文化提供口头支持。但是,她将一个稳定的功能流打包到每个冲刺的时间安排中 —— “我们不得不粉碎竞争对手 Z 所以必须达到同等的功能!” —— 在这些冲刺之间没有任何时间休息。她使用全体团队会议来赞美新的功能、大量的演示,并甚至给获得“最具开创性功能”的每个工程师奖励。因此,她的团队将功能剪辑到一个令人印象深刻的剪辑中,每一次都向董事会提供新鲜的演示,并给销售团队追求新的可能提供大量的弹药。没有性能的门,工程师一般也不会费心想太多。

  • 经理 B 采取更为平衡的做法。她知道鉴于竞争环境,以及用爆燃的 demo 打动客户和董事会成员的需要,新的功能需要一直加入。但她也担心,在她坚持的的领域,诸如性能、可靠性和质量方面会带来过多的债务。因此,她故意把她的脚放在刹车上,并推动团队在这些领域努力工作,跟她在功能上做的一样。例如,她要求良好的工程系统和内置性能遥测的新功能的当场飞行。这要求她能在绝境中控制董事会成员和产品经理,这绝对是不受欢迎和困难的。除了奖励每个全体“最具开创性功能”奖外,她还展示了性能进步图标,并给提供了最有影响的性能改善的工程师一个“性能忍者”奖。要注意,工程系统的改进也符合条件!

你认为哪一个经理会按时交付有质量的客户喜爱的产品?我对经理 B 下注。有时候你得慢下来以变快

微软最近经理了这个跟这点相关的转折:一方面,消除了之前提到的“测试”体系;在另一方面,重新关注工程系统。这是一个坎坷的策马奔腾。令人惊讶的是,要客服的最大障碍之一,根本不是个别的工程师 —— 而是经理!旧模型中“开发经理”习惯于将重点放在功能,功能,功能上!并将大部分的工程系统工作留给外包商,而大部分的质量工作都是给测试人员的。因此,他们对承认和奖励这些构建一个很好的性能文化的工作完全没有准备。结果呢?你猜对了:完全缺乏性能文化。但更微妙的是,你最终也得到了“领导力大坑”;知道最近,几乎没有高级工程师在关键工程系统上工作,以使整个团队更有生产力和能力。谁想把仅仅是分配给外包商和管理层低估的繁重工作来作为自己的职业生涯呢?再一次,你得到了你所奖励的。

早期原型有个矛盾的窘境,你不知道那些代码会不会活下来,因此在性能上一点时间都不花是个诱惑。如果你正在搞一个最小可行性产品(MVP),而你是正在烧钱的创业者,这是可以理解的。但我强烈建议不要这样做。架构很重要,一些拙劣的架构决策,在一开始就把整个摩天大楼的基础放在糟糕性能的代码上。最好将性能作为可行性研究和早期探索的一部分。

最后对上述的东西做个总结,作为大型团队的管理层,我认为有必要定期聚一聚 —— 每隔一两次冲刺 —— 来检查下管理团队的性能进展。这是在持续发生的更细粒度的工程师领导的架构层次聚会之外的。这样的检查有“大棒”的方面,但更多的是要庆祝团队的自我驱动成就,并将其保持在管理层的雷达上。这种检查应该由实验室驱动,手动生成的数字应该被认为非法。

这就将我带到了 ……

过程和基础设施(Process and Infrastructure)

“过程和基础设施” —— 多么无聊!

良好的基础设施是必须的。甚至缺乏上述文化特质的团队都不能停止在基础设施上投资;他们会生活在应该缺乏严谨的气愤之中。而良好的过程必须保证有效地使用这基础设施。下面是我看来最起码要做到的:

  • 所有的提交事先必须通过一组作为关卡的性能测试。
  • 所有滑过上面测试而导致性能倒退的提交都会被毫无疑问地回退。我称之为零容忍原则。
  • 持续的性能遥测从实验室中、运行中和现场环境中被报告。
  • 这意味着性能测试和基础设施有几个重要特性:
    • 它们不烦人。
    • 它们测量“正确”的东西。
    • 它们可以在“合理的”时间内被运行。

我有这样一句话:“如果不是自动的,那它就是没用的。”

这凸显了良好基础设施的重要性,避免了可怕的“在我电脑上明明运行得好好的”。每个人,我肯定,都曾经遇到过:测试在一些随机的机器成功运行 —— 谁知道是什么情况下 —— 被引用来宣布为成功的基准测试……只是在一段时间之后发现结果没办法保持。为什么会这样呢?

有无数的可能性。可能被一个噪音进程干扰了,像杀毒软件、搜索索引、或操作系统更新程序。也许开发人员不小心让他们多媒体播放器在后台播放着音乐。也许 BIOS 没有被正确地设定以禁用动态时钟频率调整。也许这是由于在复制和粘贴数字到一个电子表格时的一个无辜的数据录入错误。或者也许这两个比较的基准测试数字是从两个无法比较的机器配置中出来的。我都见过所有的这些在实际中发生。

任何手动的人类行为,都有可能出错。这些日子,我真的拒绝看或相信任何不是来自实验室的数字。解决方案是将一切自动化,集中精力使自动化的基础设施尽可能的好。投入你最好的那些人,使这对于团队的其他人是坚如磐石的。鼓励团队中的每个人修好破窗,并采取主动的方法来改进基础设施,并尽情地奖励这种行为。你可能会行进得慢一点,但它是值得的,相信我。

测试环(Test Rings)

当我说“所有的提交都必须通过一组性能测试”时,我作了一些掩饰,然后继续讨论了签入会怎样“滑过”所说的测试。这怎么可能呢?

现实情况是,在提交接受之前,通常不可能运行所有的测试并发现所有的问题,至少在合理的时间内是不可能的。一个好的性能工程系统应当平衡快速代码流的生产力和防止退步的保证。

对此很好的做法是将测试组织到称为“环”的东西:

  • 一个包括团队中所有开发人员在每次提交之前测量的测试的内部环。
  • 一个包括你特定子团队在每次提交之前测量的测试的内部环。
  • 一个包括开发人员自己在每次提交前自己把握执行的测试的内部环。
  • 在此之外的任意数量的依次的环:
    • 每个代码流的分支之间的关卡点。
    • 提交后的测试 —— 每晚、每周、等等 —— 基于时间/资源约束。
    • 发布前验证。
    • 发布后遥测和监控。

如你所见,在实践中如何构建这种结构是有一些灵活性的。我希望我能谎称这是一门科学,但它是一门需要明智地妥协很多因素的艺术。这是一个源源不断的讨论,管理团队应该积极参与其中。

一个小团队可能会再整个团队中以一组标准的基准来解决。较大的团队可能需要沿分支线拆分内部的环测试。不管大小如何,我们都希望主分支作为整个团队执行的最重要的性能指标,确保没有会损害这核心场景的代码流入。

在某些情况下,我们可能让开发人员自己判断来执行某些提交前的测试。(注意,这不意味着运行提交前测试全都是可选的 —— 只有它们中的某些特定的集合!)例如,这可能是一些这种情况:测试覆盖比较少用到的功能,而我们知道每夜的测试会捕获所有的提交后的退步。通常来说,当你有了一个强大的性能文化,有时候相信判断是可以的。信任,但验证。

让我们举几个具体的例子。性能测试通常根据大小在小的到大的范围内分布。这也典型造成了分别地更容易到更难找出性能退化的来源。(小测试测量的只是一个东西,因此起伏倾向更容易理解,而大测试的测量的是整个系统,波动倾向需要费上一些劲来追踪。)一个 web server 团队可能在最核心的提交前测试集中包括一系列小到大的测试范围:每请求分配的字节数(小)、请求响应时间(小)、……可能有半打其他的小到中的基准测试……,以及 TechEmpower(大),这样说行吧。感谢实验室资源、测试并行化、和超棒的 Github Webhooks,这样吧所有的这些都在 15 分钟内就完成了,很好地集成进你的 pull request 和代码审查流程。不是太差,但这显然也不是完美的。也许每个晚上,TechEmpower 跑了 4 小时来测量更长时间的性能来发现泄露。有可能开发人员通过了提交前的测试,而然后在这更长的测试中失败了。因此,团队让开发人员按需要运行这个测试,以便一个好的刺激让他/她不要出糗。但唉,错误出现了,而再次这不是一种指责和政治迫害的文化:它就是那样。

这将我带回到零容忍原则。

除非有特殊情况,否则退化应该立刻回退。在我看到的这个做成了的团队中,没有问题,也没有欠条。一旦你放松了这一立场,文化就开始崩溃了。退步层叠在一起,最终你只能永久地放弃,不管团队的最佳意图是什么。提交应该撤销,开发人员应该确定根本原因,纠正它,如果合适的话最好编写一个新的测试,然后再通过所有的火环,提交签入,这一次确保良好的性能。

测量、度量指标和统计(Measurement, Metrics, and Statistics)

体面的工程师靠直觉。好的工程师靠测量。伟大的工程师两者都干。

那么,测量什么呢?

我将度量指标分为两种不同的类别:

  • 消耗度量指标。此类通过运行测试直接测量资源消耗。
  • 观测度量指标。此类使用系统“外”的度量指标,根据观测,测量运行测试的结果。

消耗度量指标的例子有:硬件性能计数器,譬如已使用指令数、数据缓存未命中、指令缓存未命中、TLB 未命中、和/或上下文切换。软件性能计数器也有很好的候选,像 I/O 操作的数目、内存分配(和回收)、中断、和/或系统调用的数目。观测度量指标的例子包括运行时间和你云提供商记账的运行测试的花销。由于不同的原因,两者显然都很重要。

看到一个团队一次又一次孤立地测量,不禁让我洒下热泪。这对于终端用户会看到的样子是一个好的测量 —— 所以做了一个好的高层测试 —— 然而它严重缺乏它能给你的洞察力。如果没有改变的可观察性,它就是边界无用的。

消耗度量指标显然更有助于工程师试图去理解为什么有些事情改变了。在我们是上面说到的 web server 例子中,假设请求响应时间倒退了 30%。所有的测试报告告诉我们就是这个时间,这是真的。一个开发人员然后可以试着在本地重现场景,并手动排查原因,但可能单调乏味,并需要花费时间,而且由于实验室和本地硬件不一样,可能不完善。相反,如果使用指令数和内存分配两者也一起跟及时跟这次倒退报告出来会怎么样呢?根据这个,很容易看到每次请求突然比之前多出了 256KB 内存分配。意识到最近的提交,这可以使工程师很容易在更多的提交堆积到顶部从而进一步掩盖问题之前,迅速精确并及时地把罪魁祸首找出来。这就像 printf 找 bug。

说到 printf 找 bug,遥测对于长时间运行的测试是必不可少的。即使是低技术含量的像时不时(如 15 秒) printf 当前度量指标集的方法,通过检查数据库或日志文件,对追踪东西在哪里跑到了杂草中都有帮助。想象下在一个 4 小时的 web server 测试中,测试在大概 3 又 1/2 小时处脱轨了,要找出是哪里出了问题。没有持续的遥测,这可是完全令人发疯的!当然,比这做得更好也是一个好主意。产品应该又一个内置的方式来收集外面的遥测信息,并关联回关键的度量指标。StatsD 是一个极好的选项。

最后,尽可能科学地测量这些指标是很重要的。这包括跟踪标准差变异系数(CV)、和几何平均值,并使用这些来确保测试不会从一个运行到下一个发生巨大的变化。(提示:辗压了 CV 的提交应该被阻止,因为那些正是最核心的指标自身。)在你团队中有一个统计呆子也是一个好主意!

目标和基线

若你缺乏目标和基线,上面说的很少有什么用。对于每一基准/度量指标对,我推荐在你的基础设施和过程中分辨出四种不同的概念:

  • 当前值:当前的性能(可以跨多个度量指标)。
  • 基线值:产品必须在其上/下的阈值,否则测试失败。
  • 冲刺目标:团队必须在当前冲刺阶段结束时达到的目标。
  • 发布目标:团队为了发布有竞争力的功能/方案所必须达到的目标。

假设一个越高越好的指标(如吞吐量):那么通常是这种情况:发布目标 >= 冲刺目标 >= 当前值 >= 基线值。当赢了或者输了,应该不断调整。

例如,“基线棘轮”过程对于锁定改进是必要的。一种合理的方法是将基线自动棘轮于当前性能的某一百分比,最好是基于标准偏差和/或变异系数。另一种方法是要求开发人员手动进行这操作,以使所有的棘轮都是有意的和可解释。有意思的是,你可能会发现在另一个方向上棘轮也是有帮助的。即,阻止带来显著性能改进的提交,但不对基线进行棘轮。这迫使工程师们停下来思考这性能变化是不是特意的 —— 即使这样是好的!也就是,“确认你杀对了。”

当然,冲刺目标从一个冲刺到下一个保持稳定也是很常见的。所有的数字都不能一直改进。但这个系统也有助于确保团队不会在以前的成就上倒退。

我发现经冲刺目标组织到主题背后很有用。使冲刺关于“服务器性能”,或“摆脱过度分配”,或别的其他东西,给团队有凝聚力,共同的目的,并在其中加入一些乐趣。作为管理层,我们常常忘记快乐多么重要。事实证明性能可以是最有趣的;它是非常可测量的 —— 工程师喜欢这个 —— 以及,我为自己代言,它是一个相当艰难的拔出镰刀时间,并开始抽出来!它会甚至成为作为一个团队一起学习的时间,和甚至尝试一些有意思的东西,新的算法技术,像 bloom filter

不是每个性能测试都需要这种级别的严苛。任何足够重要到在提交前自动运行的测试需要这样做。而那些每日或每月的测试可能也需要。但管理所有这些目标和基线和诸如此类的东西,当它们实在太多时会变得非常麻烦。这是一个真正的风险,特别是当你正在追踪你每个基准测试中的多个度量指标时。

这就是“关键性能指标(key performance indicators)”(KPIs)变得非常重要的地方。这些性能指标非常重要,对于整个团队在任何给定的时间,整个产品有多健康,足以作为管理层面的追踪。在我过去的构建操作系统及其组件的团队中,这包括进程的启动时间,web server 吞吐量,浏览器在标准行业测试的性能,以及我们实时音频/视频客户端丢帧的数目,包括多个指标,加上上述的统计指标。这些当然在定期运行的提交前/后测试集中,但在一个地方滚动,并根据目标追踪,是巨大的关注的活动。

总结

这篇文章刚刚触及了如何做好性能工程的表面,但我希望你至少了解一件事:要做好性能全赖于又一个很好的性能文化。

这种文化需要贯穿整个组织,从管理层到工程师,和其中的每个人。它需要透明、尊重、进取、数据驱动、自我批评和不屈不挠的雄心。很好基础设施和支持过程是必须的,管理层需要欣赏和奖励这些,就跟他们在功能上做的工作一样(经常甚至更多)。只有这样,自我强化的飞轮才会开始转。

设定目标、定期沟通、执着地追踪目标和面向客户的度量指标是至关重要的。

做到我这篇文章里写的所有事情是不容易的。要记住慢下来并在这些领域设下规范真的很难,很容易欺骗自己来觉得尽可能快地前进并在以后再担心性能是正确的呼声。呃,抱歉地告诉你,虽然有时候的确是。你必须使用你的直觉和本能,然而,根据我的经验,相对于功能,我们倾向于大大地低估性能。

如果你是管理者,你的团队会感激你灌输这样的文化,而你会被奖励按时发布的更好性能的软件。如果你是一个工程师,在一个对客户的性能着迷的团队中,我保证你会花少得多的时间去救火,更多的时间主动掌握,更多的时间享受。我很乐意倾听你对于建立一个性能文化的想法和经验。