下载本文的PDF版本 PDF

关于性能的清晰思考

提升复杂软件的性能是困难的,但理解一些基本原则可以使其变得更容易。

Cary Millsap,Method R 公司

当我1989年加入 Oracle 公司时,性能——当时每个人都称之为“Oracle 调优”——是很难的。只有少数人声称他们能做得非常好,而这些人收取很高的咨询费。当环境迫使我进入“Oracle 调优”领域时,我完全没有准备。最近,我接触到了“MySQL 调优”的世界,情况似乎与我 20 多年前在 Oracle 中看到的情况非常相似。

这让我想起了我大约 13 岁时初学代数时感觉有多么困难。在那个年纪,我必须大量依靠试错法才能过关。我记得看着像 3x + 4 = 13 这样的方程式,基本上是偶然发现了答案,x = 3。

试错法对于简单的方程来说是有效的——尽管缓慢且不舒服——但随着问题变得更难,它就无法扩展了——例如,3x + 4 = 14。现在怎么办?我的问题是,我对代数还没有清晰的思考。15 岁时,James R. Harkey 老师的介绍让我走上了解决这个问题的道路。

在高中时,Harkey 先生教给我们他称之为公理化方法来解决代数方程。他向我们展示了一套每次都有效的方法步骤(并且给我们布置了大量的家庭作业来练习)。此外,通过执行这些步骤,我们在工作时必然记录了我们的思考过程。我们不仅在清晰地思考,使用可靠且可重复的步骤序列,而且我们还在向任何阅读我们工作的人证明我们正在清晰地思考。

我们为 Harkey 先生做的工作看起来像这样


这就是 Harkey 先生的公理化方法,应用于代数、几何、三角学和微积分:一次一小步,逻辑严谨、可证明和可审计。这是我第一次真正理解数学。

当然,我当时没有意识到,但证明确实是一项技能,对于我毕业后在世界上的成功至关重要。在生活中,我发现,当然,知道事情很重要,但向其他人证明这些事情更重要。没有良好的证明技能,很难成为一名优秀的顾问、优秀的领导者,甚至是一名优秀的员工。

自 20 世纪 90 年代中期以来,我的目标一直是创建一种类似严谨的方法来进行 Oracle 性能优化。最近,我一直在将这个目标的范围扩展到 Oracle 之外:“创建一种计算机软件性能优化的公理化方法。” 我发现当我说这样的话时,没有多少人喜欢,所以让我们这样说:“我的目标是帮助你清晰地思考如何优化你的计算机软件的性能。”

什么是性能?

在 Google 上搜索性能这个词,会得到超过 5 亿条结果,概念范围从自行车比赛到许多公司现在正在学习避免的可怕的员工绩效考核流程。大多数热门结果都与本文的主题相关:计算机软件执行你要求的任何任务所需的时间。

这是一个很好的起点:任务,一个面向业务的工作单元。任务可以嵌套:“打印发票”是一个任务;“打印一张发票”——一个子任务——也是一个任务。对于计算机用户来说,性能通常意味着系统执行某个任务所需的时间。响应时间是任务的执行持续时间,以每个任务的时间来衡量,例如“每次点击秒数”。例如,我使用 Google 搜索性能这个词的响应时间为 0.24 秒。Google 网页在我的浏览器中呈现了这个测量结果。这对我来说是 Google 重视我对 Google 性能的感知的证据。

有些人对另一个性能指标感兴趣:吞吐量,即在指定时间间隔内完成的任务执行次数,例如“每秒点击次数”。一般来说,负责群体绩效的人比担任个人贡献者角色的人更担心吞吐量。例如,一位会计师通常更关心每日报告的响应时间是否会导致该会计师加班。一个会计团队的经理也关心系统是否能够处理该团队中所有会计师将要处理的所有数据。

响应时间与吞吐量

吞吐量和响应时间通常具有倒数关系,但并不完全如此。真正的关系是微妙而复杂的。

示例 1. 假设你已经测量了某个基准测试的吞吐量为每秒 1,000 个任务。那么,用户的平均响应时间是多少?很容易认为平均响应时间是 1/1,000 = 每任务 0.001 秒,但事实并非一定如此。

假设处理此吞吐量的系统有 1,000 个并行、独立、同构的服务通道(也就是说,这是一个拥有 1,000 个独立、同等能力的服务提供商的系统,每个提供商都在等待你的服务请求)。在这种情况下,每个请求可能恰好消耗了 1 秒。

现在,你可以知道平均响应时间介于每任务 0 到 1 秒之间。但是,你不能仅从吞吐量测量中推导出响应时间;你必须单独测量它(我在此声明中特意包含这个词,因为有一些数学模型可以计算给定吞吐量的响应时间,但这些模型需要比吞吐量更多的输入)。

这种微妙之处也适用于另一个方向。你当然可以颠倒这个例子并证明它。然而,一个更可怕的例子会更有趣。

示例 2. 你的客户要求你正在编程的新任务在单 CPU 计算机上提供每秒 100 个任务的吞吐量。假设你编写的新任务在客户的系统上仅需 0.001 秒即可执行完成。你的程序会产生客户要求的吞吐量吗?

很容易认为,如果你可以在千分之一秒内运行一次任务,那么你肯定可以在一秒钟内至少运行该任务 100 次。你是对的,如果任务请求被很好地序列化,例如,你的程序可以在一个循环内,一个接一个地处理客户要求的 100 个任务执行。

但是,如果每秒 100 个任务从 100 个不同的用户随机到达你的系统,而这些用户都登录到客户的单 CPU 计算机上呢?那么 CPU 调度器和序列化资源(例如 Oracle 闩锁和锁以及对内存缓冲区可写访问)的可怕现实可能会将你的吞吐量限制为远低于要求的每秒 100 个任务的数量。这可能会奏效;也可能不会。你不能仅从响应时间测量中推导出吞吐量。你必须单独测量它。

响应时间和吞吐量不一定是倒数关系。要了解它们两者,你需要同时测量它们。哪个更重要?对于给定的情况,你可能会在任一方向上给出合理的答案。在许多情况下,答案是两者都是需要管理的至关重要的测量指标。例如,系统所有者可能有一个业务需求,不仅要求给定任务的响应时间在 99% 或更多的执行中必须为 1.0 秒或更短,而且系统还必须在 10 分钟的时间间隔内支持 1,000 次任务执行的持续吞吐量。

百分位规范

在上一节中,我使用了“在 99% 或更多的执行中”这个短语来限定响应时间预期。许多人更习惯于诸如“平均响应时间必须为 r 秒或更短”之类的说法。但是,用百分位来陈述需求更能映射到人类的体验。

示例 3. 假设你对每天在计算机上执行的某个任务的响应时间容忍度为 1 秒。进一步假设表 1 中显示的数字列表代表该任务 10 次执行的测量响应时间。每个列表的平均响应时间均为 1.000 秒。你认为你更喜欢哪一个?


尽管表 1 中的两个列表具有相同的平均响应时间,但列表的特性却截然不同。在列表 A 中,90% 的响应时间为 1 秒或更短。在列表 B 中,只有 60% 的响应时间为 1 秒或更短。反过来说,列表 B 代表一组用户体验,其中 40% 不令人满意,而列表 A(与列表 B 具有相同的平均响应时间)仅代表 10% 的不满意率。

在列表 A 中,第 90 个百分位的响应时间为 0.987 秒;在列表 B 中,为 1.273 秒。这些关于百分位的陈述比仅仅说每个列表代表 1.000 秒的平均响应时间更具信息量。

正如通用电气所说,“我们的客户感受到的是差异,而不是平均值。”4 将响应时间目标表示为百分位,可以制定更具说服力的需求规范,与最终用户的期望相符:例如,“跟踪发货”任务必须在至少 99.9% 的执行中在不到 0.5 秒内完成。

问题诊断

在我被邀请修复的几乎每一个性能问题中,所陈述的问题都与响应时间有关:“过去完成 X 只需不到一秒钟;现在有时需要 20 秒以上。” 当然,像这样的具体陈述通常被埋藏在其他问题的表象之下,例如:“我们的整个 [形容词已删除] 系统太慢了,我们无法使用它。”8

仅仅因为某事发生在我身上很多次,并不意味着它也会发生在你身上。你首先要做的最重要的事情是清晰地陈述问题,这样你才能清晰地思考它。

一个好的开始方法是问,你想达到的目标状态是什么?找到一些你可以衡量的具体细节来表达这一点:例如,“X 的响应时间在许多情况下超过 20 秒。当响应时间在至少 95% 的执行中为一秒或更短时,我们会感到满意。” 这在理论上听起来不错,但是如果你的用户没有如此具体的定量目标呢?这个特定目标有两个量(1 和 95);如果你的用户不知道其中任何一个呢?更糟糕的是,如果你的用户确实有具体的想法,但这些期望是不可能实现的呢?你如何知道什么是“可能”或“不可能”呢?

让我们逐步解决这些问题。

序列图

序列图是 UML(统一建模语言)中指定的一种图表类型,用于按顺序显示对象之间交互的发生顺序。序列图是可视化响应时间的一个非常有用的工具。图 1 显示了一个由浏览器、应用程序服务器和数据库组成的简单应用程序系统的标准 UML 序列图。

现在想象一下按比例绘制序列图,使得每个传入的“请求”箭头与其对应的传出的“响应”箭头之间的距离与服务请求所花费的时间成正比。我在图 2 中展示了这样一个图表。这是一个很好的图形表示,说明你的图表中表示的组件如何花费用户的时间。你可以通过查看图片“感受”到对响应时间的相对贡献。

序列图非常适合帮助人们概念化他们的响应如何在给定系统上被消耗,因为一个层将任务的控制权交给下一个层。序列图也很适合显示并发处理线程如何并行工作,并且它们是信息技术业务之外的性能分析的良好工具。10

序列图是讨论性能的一个很好的概念工具,但要清晰地思考性能,你需要其他东西。问题是这样的。假设你要修复的任务的响应时间为 2,468 秒(41 分钟 8 秒)。在这段时间内,运行该任务会导致你的应用程序服务器执行 322,968 次数据库调用。图 3 显示了该任务的序列图的样子。

应用程序层和数据库层之间有太多的请求和响应箭头,以至于你看不到任何细节。将序列图打印在很长的卷轴上并不是一个有用的解决方案,因为你需要花费数周的视觉检查才能从你看到的细节中获得有用的信息。

序列图是概念化控制流和相应的时间流的好工具。但是,要清晰地思考响应时间,你需要其他东西。

性能剖析

序列图的可伸缩性不好。为了处理调用次数巨大的任务,你需要对序列图进行方便的聚合,以便你了解你的时间是如何花费的最重要的模式。表 2 显示了一个性能剖析的示例,它可以解决问题。性能剖析是响应时间的表格分解,通常按组件响应时间贡献的降序排列。


示例 4. 表 2 中的性能剖析是初步的,但它准确地显示了你的慢任务花费用户 2,468 秒的时间在哪里。例如,使用此处显示的数据,你可以推导出性能剖析中标识的每个函数调用的响应时间贡献百分比。你还可以推导出任务期间每种类型的函数调用的平均响应时间。

性能剖析显示了你的代码将时间花在了哪里,有时甚至更重要的是,没有花在哪里。不必猜测这些事情具有巨大的价值。

从表 2 中显示的数据,你知道你的用户响应时间的 70.8% 被以下调用消耗:DB:fetch()调用。此外,如果你可以深入研究持续时间被聚合以创建此性能剖析的单个调用,你可以知道其中有多少App:await_db_netIO()调用对应于DB:fetch()调用,你可以知道其中每个调用消耗了多少响应时间。通过性能剖析,你可以开始制定问题的答案:“这个任务应该运行多长时间?” 到现在,你已经知道这是任何好的问题诊断的第一步中的一个重要问题。

阿姆达尔定律

性能剖析帮助你清晰地思考性能。即使 Gene Amdahl 在 1967 年没有给我们阿姆达尔定律,在你查看了最初的几个性能剖析之后,你可能也会自己想到它。

阿姆达尔定律指出:性能提升与程序使用你改进的东西的程度成正比。如果你试图改进的东西仅占你任务总响应时间的 5%,那么你能够产生的最大影响是你总响应时间的 5%。这意味着你在性能剖析中越往上工作(假设性能剖析按响应时间降序排序),你的整体响应时间的潜在收益就越大。

然而,这并不意味着你总是按自上而下的顺序浏览性能剖析,因为你还需要考虑你将要执行的补救措施的成本9

示例 5. 考虑表 3 中的性能剖析。它与表 2 中的性能剖析相同,只是在这里你可以看到通过为性能剖析中的每一行实施最佳补救措施,你认为可以节省多少时间,并且你可以看到你认为实施每种补救措施将花费多少成本。


你会首先实施哪种补救措施?阿姆达尔定律指出,在第 1 行实施修复具有最大的潜在好处,可以节省约 851 秒(占 2,468 秒的 34.5%)。然而,如果它真的“超级昂贵”,那么第 2 行的补救措施可能会产生更好的净收益——而这才是你真正需要优化的约束——即使响应时间节省的潜力仅为约 305 秒。

性能剖析的一个巨大价值在于,你可以准确了解对于提议的投资,你应该期望获得多少改进。它为你打开了做出更好的决策的大门,决定首先实施哪些补救措施。你的预测为你提供了衡量你作为分析师的自身表现的标尺。最后,当你以低于预期的成本找到比预期更有效地减少响应时间的更有效补救措施时,它为你提供了展示你的聪明才智和对技术的熟练程度的机会。

你首先实施哪种补救措施实际上归结于你对成本估算的信任程度。“非常便宜”是否真的考虑到了提议的改进可能对系统造成的风险?例如,更改该参数或删除该索引似乎非常便宜,但这种更改是否有可能扰乱你现在甚至没有考虑到的某些东西的良好性能行为?可靠的成本估算是你的技术技能获得回报的另一个领域。

另一个值得考虑的因素是,你可以通过创造小胜利来获得政治资本。也许廉价、低风险的改进不会带来太多的整体响应时间改进,但建立一个小型改进的跟踪记录是有价值的,这些改进完全符合你对慢任务将节省多少响应时间的预测。预测和实现的跟踪记录最终——尤其是在软件性能领域,在许多地方,神话和迷信已经盛行了几十年——为你提供了影响你的同事(你的同行、你的经理、你的客户...)所需的信誉,让他们允许你执行越来越昂贵的补救措施,这些措施可能会为企业带来更大的回报。

然而,要提醒一句:当你积累成功并提出越来越大、更昂贵、风险更高的补救措施时,不要粗心大意。信誉是脆弱的。建立它需要付出很多努力,但只需要一个粗心的错误就会毁掉它。

倾斜

当你使用性能剖析时,你会反复遇到这样的子问题

示例 6. 表 2 中的性能剖析显示,322,968DB: fetch()调用消耗了 1,748.229 秒的响应时间。如果你可以消除一半的调用,那么可以消除多少不必要的响应时间?答案几乎从来都不是“一半的响应时间”。请暂时考虑这个更简单的例子

示例 7. 对子程序的四个调用消耗了四秒钟。如果你可以消除一半的调用,那么可以消除多少不必要的响应时间?答案取决于我们可以消除的各个调用的响应时间。你可能假设每个调用的持续时间都是平均值 4/4 = 1 秒,但声明中没有告诉你调用的持续时间是均匀的。

示例 8. 想象以下两种可能性,其中每个列表代表四个子程序调用的响应时间

A = {1, 1, 1, 1}

B = {3.7, .1, .1, .1}

在列表 A 中,响应时间是均匀的,因此无论你消除哪一半(两个)调用,你都会将总响应时间减少到两秒。然而,在列表 B 中,消除哪两个调用会产生巨大的差异。如果你消除前两个调用,那么总响应时间将降至 0.2 秒(减少 95%)。如果你消除最后两个调用,那么总响应时间将降至 3.8 秒(仅减少 5%)。

倾斜是值列表中不均匀性。倾斜的可能性使你无法对我在本节开头提出的问题提供精确的答案。让我们再看一下

示例 9. 表 2 中的性能剖析显示,322,968DB: fetch()调用消耗了 1,748.229 秒的响应时间。通过消除一半的调用,你将消除多少不必要的响应时间?在不了解任何关于倾斜的情况下,你可以提供的最精确的答案是,“介于 0 到 1,748.229 秒之间。”

然而,假设你有表 4 中提供的附加信息。那么你可以制定更精确的最佳情况和最坏情况估计。具体来说,如果你有这样的信息,那么明智的做法是尝试找出如何专门消除响应时间在 0.01 到 0.1 秒范围内的 47,444 个调用。


最小化风险

在前面的几节中,我提到了修复一个任务的性能可能会损害另一个任务的性能的风险。这让我想起了一次在丹麦发生的事情。这是一个简短的故事

场景: 丹麦 Måløv 的厨房餐桌;事实上,就是著名的 Oak Table Network 的橡木桌,这是一个 Oracle 从业者网络,他们相信使用科学方法来改进基于 Oracle 的系统的开发和管理。12 大约 10 个人围坐在桌子旁,在笔记本电脑上工作并进行各种对话。

Cary: 伙计们,我快热死了。你们介意我打开窗户一会儿,让一些冷空气进来吗?

Carel-Jan: 你为什么不脱掉你的厚毛衣呢?

结束。

这里有一个优化人员都知道的一般原则:当除了你之外每个人都感到满意时,在开始处理也会影响其他所有人的全局事物之前,请确保你的局部事物井井有条。

这个原则就是为什么每当有人提议更改系统的 Oracle SQL*Net 数据包大小时,我会感到退缩,而问题实际上是几个编写不佳的 Java 程序,它们不必要地进行了大量数据库调用(因此,也不必要地进行了大量网络 I/O 调用)。如果除了一个或两个程序的用户之外,每个人都相处得很好,那么解决问题的最安全方法是将更改范围局限于那一个或两个程序。

效率

即使整个系统上的每个人都在受苦,你仍然应该首先关注业务需要首先修复的程序。开始的方法是确保程序尽可能高效地工作。效率是任务执行的总服务时间在不增加容量且不牺牲所需的业务功能的情况下可以消除多少的倒数。

换句话说,效率是对浪费的反向度量。以下是一些数据库应用程序世界中常见的浪费示例

• 中间层程序为插入到数据库的每一行创建一个不同的 SQL 语句。它执行 10,000 次数据库prepare调用(因此也执行 10,000 次网络 I/O 调用),而它本可以用一次prepare调用(因此减少 9,999 次网络 I/O 调用)来完成这项工作。

• 中间层程序进行 100 次数据库fetch调用(因此也执行 100 次网络 I/O 调用)以获取 994 行。它本可以只用 10 次fetch调用(因此减少 90 次网络 I/O 调用)来获取 994 行。

• 一条 SQL 语句(我在这里选择的措辞清楚地表明我是在 Oracle 社区中接触 SQL 的)访问数据库缓冲区缓存 7,428,322 次以返回 698 行结果集。一个额外的过滤器谓词本可以只访问数据库缓冲区缓存 52 次就返回最终用户真正想看到的七行。

当然,如果系统存在一些全局问题,导致整个系统中的大范围任务效率低下(例如,索引设计不当、参数设置不佳、硬件配置不良),那么你应该修复它。但是,不要为了适应效率低下的程序而调整系统。(诚然,有时你需要止血带以防止失血过多,但不要将权宜之计用作永久解决方案。解决效率低下问题。) 解决程序本身的效率低下问题具有更大的杠杆作用。即使这些程序是商业现成的应用程序,从长远来看,与你的软件供应商合作使你的程序高效,比尝试优化你的系统以使其在固有的低效工作负载下尽可能高效更有益。

使你的程序更高效的改进可以为系统上的每个人带来巨大的好处。很容易看出,减少浪费如何帮助修复任务的响应时间。许多人不太理解的是,使一个程序更高效会产生副作用,即系统上其他与正在修复的程序没有明显关系的程序的性能也会得到改善。这发生是由于负载对系统的影响。

负载

负载是由并发任务执行引起的对资源的竞争。这就是为什么软件开发人员进行的性能测试无法捕获所有稍后在生产中出现的性能问题的原因。

负载的一个度量是利用率,即在指定时间间隔内,资源使用量除以资源容量。随着资源的利用率上升,用户在请求该资源的服务时体验到的响应时间也会上升。任何在高峰时段在大城市乘坐汽车的人都经历过这种现象。当交通严重拥堵时,你不得不在收费站等待更长时间。

你使用的软件实际上并没有像你的汽车在交通拥堵时以 30 英里/小时的速度行驶而不是在开阔的道路上以 60 英里/小时的速度行驶时那样“变慢”。计算机软件始终以相同的速度运行,无论如何(每个时钟周期恒定数量的指令),但随着系统上的资源变得繁忙,响应时间肯定会下降。

随着负载增加,系统变慢有两个原因:排队延迟一致性延迟

排队延迟

负载和响应时间之间的数学关系是众所周知的。一种称为 M/M/m 的数学模型,将响应时间与满足一组特别有用的特定要求的系统中的负载联系起来。11 M/M/m 的假设之一是你正在建模的系统具有“理论上完美的扩展性”。这类似于拥有一个“没有摩擦”的物理系统,这是许多物理入门课程中的问题所采用的假设。

尽管存在一些过度概括的假设,例如关于完美扩展性的假设,但 M/M/m 可以教给我们很多关于性能的知识。图 4 显示了使用 M/M/m 的响应时间和负载之间的关系。

在图 4 中,你可以从数学上看到当你在不同负载条件下使用系统时的感受。在低负载下,你的响应时间与无负载时的响应时间基本相同。随着负载增加,你会感觉到响应时间略微、逐渐地下降。这种逐渐的下降实际上并没有造成太大的危害,但是随着负载继续增加,响应时间开始以一种既不轻微也不渐进的方式下降。相反,下降变得非常令人不愉快,实际上是双曲线式的。

响应时间 (R),在完美可扩展性的 M/M/m 模型中,由两个部分组成:服务时间 (S)排队延迟 (Q),即 R = S + Q。 服务时间是任务花费在消耗给定资源上的持续时间,以每个任务执行的时间来衡量,如每次点击秒数排队延迟是任务在给定资源处排队等待消耗该资源的机会所花费的时间。排队延迟也以每个任务执行的时间来衡量(例如,每次点击秒数)。

换句话说,当你在 Taco Tico 点午餐时,你获得订单的响应时间 (R) 是你在柜台前等待有人接受你的订单的排队延迟时间 (Q),加上一旦你开始与点餐员交谈后,你等待你的订单到达你手中的服务时间 (S)。排队延迟是你给定任务的响应时间与同一任务在其他方面未加载的系统上的响应时间之间的差异(不要忘记我们完美的可扩展性假设)。

膝点

当涉及到性能时,你希望从系统中获得两件事

• 你能获得的最佳响应时间:你不希望不得不等待太长时间才能完成任务。

• 你能获得的最佳吞吐量:你希望能够尽可能多地将负载塞入系统,以便尽可能多的人可以同时运行他们的任务。

不幸的是,这两个目标是矛盾的。优化第一个目标需要你最小化系统上的负载;优化第二个目标需要你最大化负载。你不能同时做到这两者。介于两者之间的某个位置——在某个负载级别(即在某个利用率值)——是系统的最佳负载。

发生这种最佳平衡的利用率值称为膝点。这是吞吐量最大化且对响应时间的负面影响最小的点。(我正在进行一场关于在这种情况下使用膝点这个术语是否合适的辩论。目前,我将继续使用它。有关详细信息,请参见下面的侧边栏。) 从数学上讲,膝点是响应时间除以利用率达到最小值的利用率值。膝点的一个优点是它发生在通过原点的直线与响应时间曲线相切的利用率值处。在精心制作的 M/M/m 图上,你只需用直尺就可以很好地定位膝点,如图 5 所示。

M/M/m 膝点的另一个优点是,你只需要知道一个参数的值就可以计算它。该参数是并行、同构、独立的服务通道的数量。服务通道是与其他相同资源共享单个队列的资源,例如收费广场中的收费亭或 SMP(对称多处理)计算机中的 CPU。

术语 M/M/m 中斜体的 小写字母 m 表示被建模系统中服务通道的数量。对于任意系统,M/M/m 拐点值很难计算,但我已在表 5 中为您计算出来,该表显示了一些常见服务通道计数的拐点值。(到目前为止,您可能想知道 M/M/m 排队模型名称中的另外两个 M 代表什么。它们与关于您的传入请求的时间随机性和您的服务时间的随机性的假设有关。有关更多信息,请参阅 肯德尔记号,或参阅Optimizing Oracle Performance11 以获得更多信息。)


为什么拐点值如此重要?对于具有随机定时服务请求的系统,允许持续的资源负载超过拐点值会导致响应时间和吞吐量发生剧烈波动,即使负载发生微小的变化。因此,在具有随机请求到达的系统上,至关重要的是管理负载,使其不超过拐点值。

拐点的相关性

这个拐点概念真的有那么重要吗?毕竟,正如我告诉您的,M/M/m 模型假设您正在考虑的系统可以完美扩展,这简直是乌托邦式的想法。我知道您在想什么:它并非如此。

M/M/m 确实为我们提供了知识,即即使您的系统确实可以完美扩展,一旦您的平均负载超过表 1 中的拐点值,您仍然会遇到大量的性能问题。您的系统不如 M/M/m 模型所描述的理论系统那么好。因此,您的系统的拐点发生的利用率值将比表 1 中的值更具约束性。(我使用拐点的复数形式,因为您可以使用一个模型对您的 CPU 进行建模,使用另一个模型对您的磁盘进行建模,使用另一个模型对您的 I/O 控制器进行建模,依此类推。)

总结

• 您系统中的每个资源都有一个拐点。

• 您每个资源的拐点都小于或等于您可以在表 1 中查到的拐点值。您的系统扩展性越差,您的拐点值就越小(越差)。

• 在具有随机请求到达的系统上,如果您允许系统上任何资源的持续利用率超过该资源的拐点值,那么您将遇到性能问题。

• 因此,至关重要的是,您要管理您的负载,以使您的资源利用率不超过您的拐点值。

容量规划

了解拐点可以简化容量规划中的许多复杂性。它的工作原理如下

• 您给定资源的目标容量是您可以舒适地在高峰时段运行任务的量,而不会将利用率驱动到超过拐点。

• 如果您将利用率保持在拐点以下,您的系统将大致呈线性运行——不会有大的双曲线惊喜。

• 但是,如果您让您的系统运行任何超出其拐点利用率的资源,那么您就会遇到性能问题(无论您是否意识到)。

• 如果您有性能问题,那么您就不需要花时间研究数学模型;您需要花时间通过重新安排负载、消除负载或增加容量来解决这些问题。

这就是容量规划如何融入性能管理流程的。

随机到达

您可能已经注意到我多次使用了术语随机到达。为什么这很重要?

有些系统具有您现在可能没有的东西:完全确定性的作业调度。有些系统——尽管现在很少见——被配置为允许服务请求以绝对机器人的方式进入系统,例如,每秒一个任务的速度。而我所说的,并不是指每秒一个任务的平均速率(例如,一秒钟两个任务,下一秒钟零个任务);我的意思是每秒一个任务,就像机器人可能将汽车零件送入装配线上的箱子一样。

如果到达您系统的请求表现出完全确定性的行为——这意味着您确切地知道下一个服务请求何时到来——那么您可以使资源利用率超过其拐点利用率,而不会必然产生性能问题。在具有确定性到达的系统上,您的目标是将资源利用率提高到 100%,而不会将太多工作负载塞入系统,以至于请求开始排队。

拐点值在具有随机到达的系统上如此重要的原因是,这些到达往往会聚集并导致利用率的暂时性峰值。这些峰值需要足够的备用容量来消耗,以便用户不必忍受明显的排队延迟(这会导致响应时间的明显波动),每次峰值发生时都是如此。

给定资源的利用率暂时性峰值超过您的拐点值是可以的,只要它们的持续时间不超过几秒钟。多少秒钟算太多?我认为(但尚未尝试证明)您至少应确保您的峰值持续时间不超过八秒。(如果您听说过“八秒规则”2,您就会认出这个数字。)当然,答案是,如果您无法满足您基于百分位数的响应时间承诺或您对用户的吞吐量承诺,那么您的峰值就太长了。

一致性延迟

您的系统不具有理论上完美的扩展性。即使我从未专门研究过您的系统,我也敢肯定,无论您现在正在考虑什么计算机应用程序系统,它都符合 M/M/m “理论上完美的扩展性”假设。一致性延迟是您可以用来模拟不完美性的因素5。它是任务花费在通信和协调对共享资源的访问上的持续时间。与响应时间、服务时间和排队延迟一样,一致性延迟以每次任务执行的时间来衡量,例如每次点击的秒数。

我不会描述用于预测一致性延迟的数学模型,但好消息是,如果您分析您的软件任务执行,您会在它发生时看到它。在 Oracle 中,以下定时事件是 一致性延迟的示例

• enqueue

• buffer busy waits

• latch free

您无法使用 M/M/m 对此类一致性延迟进行建模。这是因为 M/M/m 假设您的所有 m 个服务通道都是并行的、同构的和独立的。这意味着该模型假设,在您在 FIFO 队列中礼貌地等待足够长的时间之后,排在您前面的所有请求都已退出队列进行服务,那么就轮到您接受服务了。但是,一致性延迟并非如此。

示例 10。想象一个 HTML 数据输入表单,其中一个标记为“更新”的按钮执行 SQLupdate语句,另一个标记为“保存”的按钮执行 SQLcommit语句。像这样构建的应用程序几乎可以保证糟糕的性能。这是因为该设计使得用户有可能——实际上很可能——点击“更新”,看看他的日历,意识到“糟糕,我午餐要迟到了”,然后去吃两个小时的午餐,然后在当天下午晚些时候点击“保存”。

对于此系统上想要更新同一行的其他任务的影响将是灾难性的。每个任务都必须等待行上的锁(或者,在某些系统上,更糟糕的是:页面的锁),直到锁定用户决定继续并点击“保存”——或者直到数据库管理员杀死用户的会话,这当然会对认为他已更新行的那个人产生令人不快的副作用。

在这种情况下,任务等待锁释放的时间与系统的繁忙程度无关。它将取决于系统各种资源利用率之外存在的随机因素。这就是为什么您无法在 M/M/m 中对此类事情进行建模的原因,也是为什么您永远不能假设在单元测试类型的环境中执行的性能测试足以做出关于将新代码插入生产系统的“是/否”决定的原因。

性能测试

所有关于排队延迟和一致性延迟的讨论都引出了一个非常困难的问题:您如何才能对一个新应用程序进行充分的测试,以确信您不会因性能问题而破坏您的生产实施?

您可以建模。您可以测试1。但是,您所做的任何事情都不会是完美的。创建模型和测试非常困难,在这些模型和测试中,您可以在实际在生产中遇到这些问题之前预见到所有生产问题。

有些人允许这种观察到的徒劳性为根本不进行测试辩解。不要陷入那种心态。以下几点是确定的

• 如果您尝试在生产之前捕获问题,那么您会比根本不尝试捕获更多的问题。

• 您永远无法在预生产测试中捕获所有问题。这就是为什么您需要一种可靠且高效的方法来解决在您的预生产测试过程中泄漏出来的问题。

介于“不测试”和“完全生产仿真”之间的是适当的测试量。飞机制造商的适当测试量可能比销售棒球帽的公司的适当测试量更多。但不要完全跳过性能测试。至少,您的性能测试计划将使您在修复生产运行期间不可避免地发生的性能问题时,成为更有能力的诊断师(和更清晰的思考者)。

测量

人们感受到吞吐量和响应时间。吞吐量通常很容易测量,响应时间则要困难得多。(请记住,吞吐量和响应时间不是倒数关系。)用秒表测量最终用户操作可能并不困难,但获得您真正需要的可能非常困难,即能够深入了解给定响应时间如此之大的原因的细节。

不幸的是,人们倾向于测量容易测量的东西,但这不一定是他们应该测量的东西。这是一个错误。不是您需要的,但很容易获得并且似乎与您需要的相关的度量称为替代度量。示例包括子例程调用计数和子例程调用执行持续时间的样本。

我很惭愧,我的母语水平不够高,只能这样说,但这里有一种朗朗上口、现代的方式来表达我对替代度量的看法:替代度量很糟糕。

在这里,不幸的是,糟糕并不意味着永远不起作用。如果替代度量永远不起作用,实际上会更好。那样就不会有人使用它们了。问题是替代度量有时起作用。这激发了人们的信心,认为他们使用的度量应该一直起作用,但事实并非如此。替代度量有两个大问题。

• 它们可能会告诉您您的系统没问题,但实际上它有问题。这就是统计学家所说的 I 类错误,假阳性。

• 它们可能会告诉您某件事是问题,但实际上它不是问题。那是 II 类错误,假阴性。我见过每种类型的错误都浪费了人们多年的时间。

当需要评估真实系统的具体情况时,您的成功与您的系统允许您获得的度量有多好息息相关。我很幸运能在 Oracle 市场领域工作,我们这个宇宙中心的软件供应商积极参与使以正确的方式测量系统成为可能。让应用软件开发人员使用 Oracle 提供的工具是另一回事,但至少产品中存在这些功能。

性能是一项功能

性能是软件应用程序的一项功能,就像认识到 “Case 1234” 形式的字符串自动超链接到您的错误跟踪系统中的 case 1234 一样方便。(FogBugz,我喜欢使用的软件,就是这样做的。)性能,像任何其他功能一样,不会凭空发生;它必须被设计和构建。要做好性能,您必须考虑它、研究它、为其编写额外的代码、测试它并支持它。

然而,与许多其他功能一样,在您仍在编写、研究、设计和创建应用程序时,您无法确切知道性能会如何。对于许多应用程序(可以说,对于绝大多数应用程序),性能在软件开发生命周期的生产阶段之前是完全未知的。这给您留下了这样的结果:由于您无法知道您的应用程序在生产中将如何执行,因此您需要编写您的应用程序,以便在生产中修复性能很容易。

正如 David Garvin 教导我们的那样,管理容易衡量的东西要容易得多3。编写一个在生产中易于修复的应用程序,首先要从一个在生产中易于测量的应用程序开始。

通常,当我提到生产性能测量的概念时,人们会陷入对性能工具的测量侵入效应的担忧状态。他们立即进入数据收集妥协模式,只留下替代度量。具有额外代码路径来测量时间的软件是否会比没有该额外代码路径的同一软件慢?

我喜欢我曾经听 Tom Kyte 回答这个问题的一个答案7。他估计 Oracle 广泛的性能工具的测量侵入效应为 -10% 或更低(其中或更低意味着或更好,例如 -20%、-30% 等)。他继续向一位现在感到困惑的提问者解释说,由于 Oracle 公司从其性能工具代码中获得的知识,该产品现在至少快了 10%,这完全弥补了工具可能造成的任何“开销”。

我认为供应商往往花费太多时间担心如何使他们的测量代码路径高效,而没有首先弄清楚如何使其有效。这完全符合 Knuth 在 1974 年写下的想法,他说“过早优化是万恶之源”6。将性能测量集成到产品中的软件设计师更有可能创建一个快速应用程序,更重要的是,创建一个随着时间推移会变得更快的应用程序。
Q

关于拐点的公开辩论

在本文中,我撰写了关于性能曲线中的拐点、它们的相关性以及它们的应用。然而,甚至是否值得尝试定义拐点的概念,至少在 20 年前就已成为争论的主题。

对于我所描述为拐点的东西实际上并没有真正意义的想法,存在重要的历史依据。1988 年,Stephen Samson 认为,至少对于 M/M/1 排队系统,性能曲线中没有出现“拐点”。Samson 写道:“指导数字的选择并不容易,但经验法则制定者仍在继续。在大多数情况下,没有拐点,无论我们多么希望找到它。”3a

整个问题让我想起了,正如我在 1999 年写的那样2a,青蛙和沸水的寓言。故事说,如果你把一只青蛙扔进一锅沸水,它会逃脱。但是,如果你把一只青蛙放进一锅冷水里,然后慢慢加热,那么青蛙会耐心地待在原地,直到它被煮死。

对于利用率,就像沸水一样,显然存在一个“死亡区”,一个您无法承受运行具有随机到达的系统的值范围。但是,死亡区的边界在哪里?如果您试图实施管理利用率的程序化方法,您需要知道。

我的朋友 Neil Gunther 私下与我争论说,首先,在没有函数不连续性的情况下,术语拐点完全是错误的用词。其次,他断言,对于 M/M/1 系统,0.5 的边界值浪费得太低,您应该能够在比该值高得多的利用率值下成功运行这样的系统。最后,他认为,任何这样的特殊利用率值都应明确定义为利用率值,超过该值,您的平均响应时间将超过您对平均响应时间的容忍度(图 A)。因此,Gunther 认为,任何有用的不超过利用率值都只能从关于人类偏好的询问中得出,而不是从数学中得出。(有关他的论点的更多信息,请参阅此处。)

我在这个论点中看到的问题如图 B 所示。想象一下,您对平均响应时间的容忍度为 T,这创建了一个最大容忍利用率值 ρT。请注意,即使 ρT 附近的平均利用率发生微小的波动,也会导致平均响应时间发生巨大的波动。

我相信您的客户感受到的是方差,而不是均值。也许他们他们会接受高达 T 的平均响应时间,但是当一分钟内平均利用率发生 1% 的变化导致该期间的平均响应时间增加十倍时,人类将无法容忍系统的性能。

我确实理解这种观点,即我在本文中列出的拐点值低于许多人认为可以安全超过的利用率值,尤其是对于 M/M/1 等低阶系统。但是,重要的是要避免在平均利用率值下运行资源,在这些平均利用率值下,利用率的微小波动会导致响应时间的过大波动。

话虽如此,我还没有一个对过大波动的良好定义。也许,就像响应时间容忍度一样,不同的人对波动有不同的容忍度。但也许存在一个波动容忍因子,它在所有用户中都具有合理的普遍适用性。例如,Apdex 应用性能指数标准假设用户变得“沮丧”的响应时间 F 普遍是他们的态度从“满意”转变为仅仅“容忍”的响应时间 T 的四倍1a

无论您如何定义拐点或我们最终称其为什么,拐点都是我在本文正文前面描述的容量规划程序的重要参数,而且我认为它是计算机系统工作负载日常管理过程的重要参数。

我将继续研究。

参考文献

1a. Apdex;http://www.apdex.org

2a. Millsap, C. 1999. 性能管理:神话与事实;http://method-r.com

3a. Samson, S. 1988. MVS 性能传奇。载于计算机测量组会议论文集:148-159。



参考文献

1. CMG(计算机测量组,一个非常非常认真地研究这些问题的专业人士网络);http://www.cmg.org

2. 八秒规则;http://en.wikipedia.org/wiki/Network_performance#8-second_rule

3. Garvin, D. 1993. 构建学习型组织。哈佛商业评论(七月)。

4. 通用电气公司。什么是六西格码?通往客户影响的路线图。  http://www.ge.com/sixsigma/SixSigma.pdf

5. Gunther, N. 1993. 计算可扩展性通用定律;http://en.wikipedia.org/wiki/Neil_J._Gunther#Universal_Law_of_Computational_Scalability

6. Knuth, D. 1974. 使用 Go To 语句的结构化编程。 计算调查 6(4): 268。

7. Kyte, T. 2009. 几个链接和一个广告...;http://tkyte.blogspot.com/2009/02/couple-of-links-and-advert.html

8. Millsap, C. 2009. 我的整个系统都很慢。现在怎么办? http://carymillsap.blogspot.com/2009/12/my-whole-system-is-slow-now-what.html

9. Millsap, C. 2009. 诊断先于解决的重要性。  http://carymillsap.blogspot.com/2009/09/on-importance-of-diagnosing-before.html

10. Millsap, C. 2009. 使用全局入口进行性能优化。或者不? http://carymillsap.blogspot.com/2009/11/performance-optimization-with-global.html

11. Millsap, C., Holt, J. 2003. 优化 Oracle 性能。塞瓦斯托波尔,加利福尼亚州:奥莱利。

12. Oak Table Network;http://www.oaktable.net

致谢

感谢 Baron Schwartz,在电子邮件对话中,您认为我在帮助您,但实际上,您在帮助我理解需要更突出地将一致性延迟引入我的思考。感谢 Jeff Holt、Ron Crisco、Ken Ferlita 和 Harold Palacio,感谢你们的日常工作,使公司得以运转,感谢你们的午餐谈话,使我的想象力得以驰骋。感谢 Tom Kyte,感谢您持续的启发和支持。感谢 Mark Farnham,感谢您的有益建议。感谢 Neil Gunther,感谢您在关于拐点的持续讨论中的耐心和慷慨。

Cary Millsap 是 Method R Corporation (http://method-r.com) 的创始人兼总裁,该公司致力于软件性能。他是(与 Jeff Holt 合著)Optimizing Oracle Performance (O'Reilly) 的作者,也是 Oracle Insights: Tales of the Oak Table (Apress) 的合著者。他曾任 Oracle Corporation 系统性能组副总裁,也是 Hotsos 的联合创始人。他还是 Oracle ACE Director 和 Oak Table Network 的创始合伙人,Oak Table Network 是著名的“Oracle 科学家”的非正式协会。他的博客地址是 http://carymillsap.blogspot.com,他的 Twitter 地址是 http://twitter.com/CaryMillsap

acmqueue

最初发表于 Queue vol. 8, no. 9
数字图书馆 中评论本文





更多相关文章

David Collier-Brown - 您对应用程序性能一窍不通
每当您遇到性能或容量规划问题时,您都不需要进行全面基准测试。一个简单的测量将提供您系统的瓶颈点:这个示例程序在每 CPU 每秒八个请求后会显着变慢。这通常足以告诉您最重要的事情:您是否会失败。


Peter Ward, Paul Wankadia, Kavita Guliani - 重塑 Google 的后端子集
后端子集对于降低成本非常有用,甚至对于在系统限制内运行可能是必要的。十多年来,Google 一直使用确定性子集作为其默认后端子集算法,但是,尽管此算法平衡了每个后端任务的连接数,但确定性子集具有高水平的连接流失。我们在 Google 的目标是设计一种具有减少连接流失的算法,该算法可以取代确定性子集作为默认后端子集算法。


Noor Mubeen - 工作负载频率缩放定律 - 推导和验证
本文介绍了与每个 DVFS 子系统级别的工作负载利用率缩放相关的方程。建立了频率、利用率和比例因子(其本身随频率变化)之间的关系。这些方程的验证结果证明是棘手的,因为工作负载固有的利用率也似乎以未指定的方式在治理样本的粒度上变化。因此,应用了一种称为直方图脊迹的新方法。当将 DVFS 视为构建块时,量化缩放影响至关重要。典型应用包括 DVFS 调控器和/或其他影响系统利用率、功耗和性能的层。


Theo Schlossnagle - DevOps 世界中的监控
监控可能看起来非常令人生畏。要记住的最重要的事情是,完美绝不应成为更好的敌人。DevOps 使组织内部能够进行高度迭代的改进。如果您没有监控,请获取一些;获取任何东西。有总比没有好,如果您已经拥抱了 DevOps,那么您已经注册了使其在一段时间内变得更好。





© 保留所有权利。

© . All rights reserved.