您在一家软件公司的产品开发部门工作,该公司的产品经常在性能方面与竞争对手进行比较。 性能是您业务的重要组成部分; 但添加新功能、修复错误和从事新项目也很重要。 那么,您如何领导您的团队开发高性能软件,同时还要完成所有其他工作? 在维护和增强的周期中,您如何保持高性能?
如果性能对您的业务至关重要,您的员工需要知道。 在您的整个开发团队中,您都在寻求一种平衡的性能文化——而不是那种开发人员花费数小时进行性能分析和优化到深夜,导致错过截止日期的文化,而是那种人们有权评论某个特定功能有点慢的文化。 开发人员需要相信,从根本上说,他们的产品是高性能的,如果不是,那就是出了问题。
不幸的是,许多组织将其开发人员与商业压力隔离开来; 他们不希望他们的技术人员花费宝贵的开发时间在售前工作上,或者他们不希望向大量员工透露敏感信息。 然而,关于性能约束或比较的售前信息非常有价值,开发团队需要了解这些信息; 当他们意识到客户正在考虑替代解决方案,因为它在给定的基准测试中速度更快时,没有什么比这更能激励他们了。 当客户基于产品性能签约时,请告诉您的开发人员; 当您的产品正在与竞争对手进行基准测试时,请告诉他们; 并且不要害怕告诉他们您的产品比竞争对手慢。
您试图做的是将高性能的想法根植于潜意识层面。 您显然不希望您的开发人员认为性能是最重要的事情; 对于几乎所有应用程序来说,情况并非如此。 然而,除非每个人都对性能的重要性有所认识,否则您可以预期随着应用程序的维护,性能会逐渐下降。 后续版本将需要更多内存,启动速度稍慢,或者响应速度稍慢。 虽然这对您的新客户来说不是问题,他们可能正在购买最新、最快的硬件来运行您的应用程序,但这会给您的现有客户群带来痛苦——可能是新版本将不再适合现有的硬件平台,或者关键业务操作突然花费的时间稍长。
性能设计是一个有争议的领域; 有些人认为您必须始终从性能设计开始,而另一些人则认为您应该从可以工作的东西开始,然后在以后对其进行优化。 这两种方法都有其优点; 一如既往,关键在于找到两者之间的正确平衡。
高层设计决策通常很难更改,因此从根本上来说很难优化。 因此,在这个层面上,您必须考虑性能——主要组件之间的接口、公共 API 和数据库模式都属于这一类——尤其是在修改使升级变得困难的情况下。 较低级别的设计点——例如,私有的、非持久性的数据结构——更容易更改,因此最好从易于理解的东西开始,并在事实证明有问题时对其进行优化。
此时,您需要记住,您已经告诉所有人性能很重要,因此可以信任他们以性能为中心来实现这些低级别细节。 您现在的工作是鼓励实验:与其进行理论化,不如要求开发人员拼凑 50 行的测试平台,以对比解决同一问题的不同方法。 如果某个特定的算法或数据结构是基于性能或效率选择的,请要求查看证据——并说明原因。 您不是试图证明任何人是错的或让他们看起来很傻; 您只是想知道他们已经考虑过这个问题,并且可以证明他们的决策是合理的。 更重要的是,您希望人们用实验证据来支持这些想法; 您不希望根据在旧平台或以前的工作中获得的经验或偏见来做出决策。
在审查一些代码时,我曾经看到一个潜在的严重性能问题,可以通过这种实验方法来避免。 哈希表中使用了短字符串值键,其中哈希算法的编写方式使其只能返回 4,096 个可能的值之一。 这尤其奇怪,因为程序员已经将表格的大小调整为数万个项目; 他显然期望的唯一键值远远超过 4,096 个,这使得算法的选择看起来很奇怪,并且可能导致性能下降。 不仅如此,哈希函数似乎比我们从教科书中提取的标准字符串哈希算法更复杂,因此速度更慢。
当我向程序员询问此事时,他说:“看起来不错。 我在很多地方都使用过这种哈希算法。”
“所以你没有考虑使用我们的标准算法?” 我继续问道。
“不,这个更好。”
“为什么?”
“哦,我不记得了。 我想我曾经测试过它们,发现有一种情况下我的更好。”
当我运行一些自己的测试时,我表明,对于实际的工作负载,标准算法更快,并且始终生成更广泛的键分布。
这个例子将我们带到了代码审查。 代码的同行评审是一种良好的实践,我相信您无论如何都在这样做; 但是您在这样做时是否考虑了性能? 您的审阅者是否正在寻找性能关键区域的潜在问题? 同样,您需要取得平衡:并非所有内容都需要高性能,并且在看到问题之前优化某些内容是没有意义的; 但是审阅者可能会发现一种数据结构,该数据结构在某些类别的生产数据上可能会变得效率低下,或者可能能够定位性能测试。
请记住,只有两种方法可以使软件运行得更快:减少其执行的操作; 并更快地完成您正在做的事情。
大多数人——尤其是经验不足的员工——会直接投入并尝试后者,因为这是一个更大的智力挑战,并且程序员的心态是将问题分解为小的、可管理的部分。 然而,最大的收获通常在于前者; 您无法使 strcmp(3C) 更快,那么减少调用它的次数怎么样?
这方面的一个例子是我维护了多年的代码片段,该代码片段用于根据用户指定的字段选择满足数据库查询的相关索引。 这个特定的查询引擎可以接受多个子查询,逻辑上使用 OR 运算符组合在一起以形成更大的条件。 从历史上看,它的工作方式是将查询拆分为子查询,然后为每个子查询选择一个索引。 这很简单,但可能不是最优的——对于大量子查询,它可能会变慢。
有一段时间,我一直在不断改进,逐步加快为每个子查询选择索引的代码的速度。 从一个版本到另一个版本,我会减少 10% 或 20%,当您将它们加在一起时,这非常重要。 然而,我可以做出的最佳优化是减少运行此代码的次数——我发现,在许多情况下,子查询最终选择了相同的索引。 如果我可以预先将相似的子查询分组在一起,我可以大大减少调用索引选择代码的次数。 这不是 10% 或 20% 的改进; 这一更改使最佳情况下的性能提高了 100 倍!
由此得出的结论是,您需要仔细研究应用程序的常见用例,这可能会以其他用例为代价。 顾名思义,这是执行次数最多的代码路径,因此优化它对整体性能的影响最大。
最大的性能改进几乎总是在软件堆栈的最高层,因此当您优化系统时,请考虑性能关键部分的架构; 将其分解为步骤并询问:我们真的需要执行这些步骤中的每一个吗? 对于最常见的情况,我们真的需要执行这些步骤中的每一个吗?
虽然本文不是关于优化软件本身,但作为高级工程师或产品经理,您在为性能工作分配资源时需要考虑这些领域。 如果有初级员工参与,请确保您已指导他们查看问题的正确部分。
有些工程师在性能方面具有很强的直觉。 他们可以查看问题,浏览一些最喜欢的统计运行,并准确预测应该如何修复它。
以我的经验,我们谈论的是极少数具有这种直觉的工程师。 我们绝大多数凡人的直觉都很糟糕:我们几乎总是错的,并且可能会浪费大量时间猜测,而且猜测错误; 更糟糕的是,我们通过优化系统的错误部分引入了不必要的风险。 我们应该依靠我们行业的工具,并开发一种系统的方法来解决问题。
我们这些对性能感兴趣的人应该熟悉我们选择的平台上的性能分析工具。 许多商业编译器套件都包含某种性能分析器(例如,Sun Studio 编译器集合中的性能分析器),并且还有其他独立的可用产品(例如 Rational Quantify 和 Intel VTune)。 如果没有 DTrace,任何有用的工具列表都不完整,DTrace 是 Solaris 中的动态跟踪框架,它在查看系统性能问题方面是独一无二的,它可以检查整个环境,而不是特定的进程或代码片段。
您无疑有自己喜欢的工具,因此关键信息不是推荐我自己的工具,而是了解这些工具并系统地应用它们。 当您进行优化时,请重复相同的运行并使用从这些工具中获得的客观指标来衡量性能改进。
如果您想继续生产高性能软件,您必须能够运行可重现、可比较的性能测试。 理想情况下,您将拥有专用的标准硬件来运行这些测试; 这应该是您客户在生产中运行的硬件的代表,即使不是直接可比的。 您将在发布周期中运行一组基本的性能测试,并在需要时运行更全面的基准测试。
那么您应该测试什么? 什么才是重要的? 您需要在运行测试所需的时间和它们实际提供给您的信息之间找到平衡。 一大组复杂的测试可以告诉您关于您的应用程序的大量信息,甚至可以帮助您追踪导致性能下降的区域,但这对于每个版本都运行来说可能太耗时了。 可以在不到一小时内自动运行的更简单的测试会更好。 此外,您的测试需要使用在版本之间稳定的公共接口来衡量某些内容; 否则,维护测试将成为一种开销。
当然,测试必须执行对您的客户重要的操作和代码路径。 它们必须根据生产系统中看到的数据集和负载类型来衡量常见事务或查询的吞吐量。 如果可行,可以按需重新运行的捕获的生产工作负载将是理想的选择。
发布基准通常看起来像是自找麻烦。 您的客户会阅读它们,这就是他们期望的性能。 在某种程度上是这样——基准很容易被误解——但对性能感兴趣的客户足够明智,他们意识到基准不一定是他们应该期望的绝对真实世界性能的指标; 但是,它们是相对性能的有用指标。
发布基准测试结果是您在组织内部传播性能福音的最大武器之一。 它向公司工作的每个人展示了产品可以实现的性能类型——并且表明您认真对待衡量和提高性能。 最棒的是,结果开始了与客户的讨论。 如果您的基准测试结果很差,或者它们是在不切实际的硬件上运行的,或者工作负载不合适,他们很快就会告诉您。
理想情况下,您发布的基准测试结果将是您在发布时运行的测试的输出。 如果不是,您将需要投入资源来保持已发布基准的更新。
您需要为客户常见的用例进行工程设计; 您的基准和发布测试需要尽可能代表实际工作负载; 您的设计和实施决策需要为生产数据集找到正确的折衷方案。 对于这些中的每一个,熟悉生产系统至关重要。 您需要了解您的软件在现场的使用情况,观察它在哪里被拉伸,并根据整个客户群的行为得出结论。
最重要的是,您需要了解您的客户正在使用的硬件,以及它如何应对他们的工作负载。 您需要熟悉健康系统的生理机能,并查看一些真实世界的问题,以便您可以看到警告信号何时开始出现。 您需要对真实系统有良好的“感觉”,以及它们是受 CPU、内存、磁盘还是网络限制,以及哪些业务操作对性能至关重要。 您需要熟悉各种不同的工作负载,以及它们是否需要不同的调整或配置设置。
理解客户的生产系统的重要性怎么强调都不为过。 更重要的是,您需要实际获得这种理解的经验。 它应该深入了解监控工具和仪表如何帮助您找到问题。 并非组织中的每个人都需要这种理解,但总有人需要,并且在设计未来版本和基准时,您需要利用这种理解。 在最高层面上,正是这种理解推动了性能方面的架构变革。
为了实现对生产系统的理解,您需要在应用程序中添加一些仪表。 在某种程度上,您需要知道系统正在做什么:它正在执行什么类型的操作,它们需要多长时间等等。 显然,这种仪表需要是轻量级的——开销必须可以忽略不计——理想的情况是在每个系统上自动启用和记录关键统计信息。 这允许您在特定事件(例如,非常繁忙的一天或用户报告的问题)之后查看系统并查看这些统计信息,而不是必须启用它们并等待它再次发生。
同样,了解健康系统的模式可以帮助您通过统计数据快速发现问题。 例如,如果您记录执行的事务数和每个数据库表上的读取次数,您可以快速发现某个特定事务何时过多地读取给定的表。 这可能是您追踪特定问题所需的线索。
软件行业对重用概念非常热衷——重用我的脚本,通过 API 重用我的代码,重用我的设计方法。 我们似乎不太擅长分享的是问题的病理学——这对于性能问题尤其如此。
在您的组织内部,在您编写的应用程序中,您的每位工程师都在获得关于您使用的技术中哪些是快速的,哪些不是快速的经验。 那些关心性能并密切关注性能的人知道哪些是速度最快、内存效率最高的数据结构; 他们知道哪些类型的数据结构总是会引起问题; 他们了解您使用的库的怪癖以及如何处理它们。 分享这种技术知识库是困难的; 做得不好可能会让经验不足的员工感到困惑。 同样,代码审查也发挥着作用。 您更有经验的工程师将能够反馈他们不喜欢的外观并提出更好的替代方案。
生产系统上的性能问题也需要公开,以便您的整个组织可以考虑下次如何避免它们。 也许客户做了一些有点出乎意料的事情——但为什么呢? 您是否需要记录更好的替代方案,以便客户将来不再这样做? 或者这是一种合理的商业实践,您需要更好地支持它?
性能是一个专业领域; 如果它对您的业务很重要,您可能需要在您的开发和支持团队中安排性能专家,他们可以利用他们的经验快速解决问题。 然而,这些人只是先锋。 如果性能很重要,整个开发团队都需要意识到并参与其中。 一两位技术精湛的性能专家可以在出现问题时为您指明正确的方向,但他们无法优化每一段代码。
性能不是每个人的首要任务; 如前所述,对于大多数应用程序而言,性能充其量只是大量竞争且可能相互冲突的需求之一。 在资源有限的情况下,始终很难确定优先级,并且必须有所让步; 如果没有立即的商业压力来加快应用程序的速度,那么让步点可能是性能。
您只需要继续尝试:继续提出改进建议,并努力使性能始终受到每个人的关注。 也许最重要的是,您需要具有战略意义。 考虑如何更改您的产品以避免您在现场看到的问题或从根本上提高性能。 也许这可能包括技术变革——转向 64 位或网格范例——或某种业务逻辑的重组,以减少常见情况的不必要处理。 确定瓶颈在哪里,并考虑如果您使用一张白纸来消除它们,您会怎么做; 然后查看如何将更改合并到您当前的设计中,或者如何在选定的方向上迁移。
最后,与软件中的其他方面一样,性能完全关乎平衡。 应用程序运行速度与您为优化它所付出的努力之间存在权衡——因此,间接地,与您为实现其他功能所付出的努力之间也存在权衡。 您的组织需要找到正确的折衷方案并相应地确定性能的优先级。
开发高性能软件是困难的; 除了技术上的困难之外,它还带来了管理和运营方面的挑战。 组织需要巧妙地改变他们营销、设计、构建和支持其产品的方式,以保持生产满足客户性能要求的软件。
PHILIP BEEVERS 负责管理 royalblue 的基础设施开发团队,royalblue 是一家提供市场领先的高性能金融交易系统的软件公司。 他在该应用领域工作了九年,从事专有的 64 位内存数据库、低延迟中间件和事件通知以及更通用的性能分析工作。 他毕业于牛津大学,获得数学学士学位。
最初发表于 Queue vol. 4, no. 1—
在 数字图书馆 中评论本文
David Collier-Brown - 您对应用程序性能一窍不通
每当您遇到性能或容量规划问题时,都不需要进行全面的基准测试。 一个简单的测量将提供您系统的瓶颈点:这个示例程序在每个 CPU 每秒处理八个请求后,速度会明显变慢。 这通常足以告诉您最重要的事情:您是否会失败。
Peter Ward, Paul Wankadia, Kavita Guliani - 在 Google 改造后端子集
后端子集对于降低成本非常有用,甚至对于在系统限制范围内运行可能是必要的。 十多年来,Google 使用确定性子集作为其默认后端子集算法,但尽管此算法平衡了每个后端任务的连接数,但确定性子集具有高水平的连接流失。 我们在 Google 的目标是设计一种连接流失减少的算法,该算法可以取代确定性子集作为默认后端子集算法。
Noor Mubeen - 工作负载频率缩放定律 - 推导和验证
本文介绍了与每个 DVFS 子系统级别的工作负载利用率缩放相关的方程。 建立了频率、利用率和比例因子(其本身随频率变化)之间的关系。 这些方程的验证结果证明是棘手的,因为工作负载固有的利用率似乎也在治理样本的粒度上以未指定的方式变化。 因此,应用了一种称为直方图脊迹的新方法。 当将 DVFS 视为构建块时,量化缩放影响至关重要。 典型应用包括 DVFS 调控器和或影响系统利用率、功耗和性能的其他层。
Theo Schlossnagle - DevOps 世界中的监控
监控似乎非常繁琐。 要记住的最重要的事情是,完美永远不应成为更好的敌人。 DevOps 使组织能够进行高度迭代的改进。 如果您没有监控,请获取一些; 获取任何东西。 有总比没有好,如果您已经接受了 DevOps,那么您已经注册了随着时间的推移使其变得更好。