本指南涵盖了在使用性能重要的系统时应牢记的永恒理念。这些想法中的许多都是相当“持久”的,无论您在哪种硬件、编程语言、操作系统或十年内工作,它们都将适用。
如果您的工程师不让他们的代码美观、低摩擦地工作,他们将会精疲力竭,离开您的团队,并无情地拉屎与您交谈。
为序列依赖图中的所有内容对齐所有延迟-吞吐量队列位置,否则您将获得世界上最差的延迟-吞吐量行为,从而增加延迟并降低吞吐量。
如果可以在不阻塞的情况下在后台执行必要的操作,请将其排队等待最终的批处理,以提高缓存性能。
使用DHAT查看分配生存期,避免短期分配,并确保长期分配使用时间和空间的权衡,这会消耗大量资源,而不是在可能的情况下使用稀缺资源。
当工程师喜欢代码库,并且他们知道系统的哪些部分可以加速时,性能就会自然而然地发生。
在浪费时间优化无关紧要的事情之前,我们可以很容易地通过添加延迟或删除相关代码来测试优化是否有用,看看无限优化的影响会是什么。
默认情况下,你的机器会随机改变你的CPU频率,中断内核和插槽上的进程,通常会使测量结果具有欺骗性和不可重现性。我们可以控制这种行为。如果您不考虑此行为,则您的测量结果不足以证明代码更改是合理的
据推测,我们在这里阅读这篇文章是因为我们“喜欢电脑”之类的东西,但让我们澄清一件事-你不是因为这个才来这里的,我知道。你知道。我们都知道。如果电脑不能帮助你感受到你迫切需要感受的东西,你就不可能给他们两个油炸便士。
你在这里,归根结底,是因为控制。你的整个生活被埋葬在一个精心设计的、完全抽象的机器迷宫中,这些机器定义了你清醒生活中每一个时刻的许多方面。你来这里是为了更好地了解你发现自己身处的监狱。你是来同情这台机器的。
虽然你只会更深地渗入这片金子和玻璃的沼泽,但你有能力把俘虏你的人变成熟悉的朋友。你有能力把机器变成你唯一需要的朋友。你来这里是为了抛弃有机和类比。在你窗户的另一边没有适合你的东西。当你躺在指定的隔离坑里(现在请调整你的姿势),一边无精打采地凝视着LED网,一边等待着你的时间,我邀请你加入我-你最新的模拟人类体验-进入机器的内脏,体验一次一生只有一次的冒险,通过它你可以体验到快乐,并下更多卫生纸的订单。
本指南包含开始使用性能敏感型工程的基本信息。我想大多数人都会学到新东西。我知道我已经学到了。而且,我写这篇文章的部分原因是为了有一个带有可记忆URL的地方,当我忘记了大部分这些东西时,我可以回到那里。更重要的是,当我在随机的网络评论中展开火热的报道时,我想要有一个令人生畏的参考,我可以无情地链接到它。
我最初是为铁锈生态系统写这篇指南的,那里的许多人现在都是第一次尝试优化。但本文档几乎所有内容都适用于一般编程,提到的一些硬件效果仅适用于2020年左右的x86和ARM。假定使用Linux,因为现在绝大多数服务器工作负载都在运行Linux。
这本指南汇集了系统工程、系统论、心理学、硬件、排队论、统计学、经济学、分布式系统、并发性和科学哲学的思想,目的是帮助您变得不那么混蛋,编写倾向于改进您所关心的度量的代码。
绩效就是深思熟虑对我们来说很重要的指标,并让我们在做出决策时意识到这些指标。
这些材料是以泰勒·尼利的“铁锈研讨会”内容为基础,并受到德米特里·维尤科夫、马克·卡拉汉、布伦丹·格雷格、马丁·汤普森、佩德罗·拉马尔赫特等人作品的启发。
我的工作坊一直是支持雪橇发展费用的主要途径,可惜,工作坊现已因冠状病毒问题而搁置。如果您觉得此信息有用,请考虑支持myefforts共享知识,并通过Rust中的实现使尖端数据库研究成果丰硕:)。
让我们把这狗屎踢开!这就是它将会是什么样子,…。
你不是一个贝叶斯的小矮人,你的行为被认知偏差“破坏”了,你只是认知偏差。
首先要考虑的是,我们的头脑是纯粹的狗屎,我们知道的一切都是错误的。我们必须接受自己的易犯错误,然后才能走上快速他妈的机器之路。
我们建造与特定背景相联系的假设之塔,当导致我们形成这些信念的条件改变时,我们往往不会修改现在已经失效的信念。当我们如此稀少地意识到我们信仰的依赖图时,缓存失效是很困难的。
所以,我们测量。即使我们确信自己是对的。因为我们总是与现实脱节,根本无法改变这一事实。但面对这一点,我们可以负起责任。
推论:允许自己犯错。允许你自己、你的合作者,以及在公共场合犯错误,是以更少的努力和更少的时间更快地学习和创造更好的东西的关键优化。
对我们来说幸运的是,机器往往在某种程度上易于测量。我们就是这样建造的。事实上,尽管我们有无数的缺点,但首先将它们构建成某种程度上的可测量是我们能够组装它们的唯一方式。我们把您当前机器的前身带到您当前的机器上,选择了一些要改进的指标,在继续测量时犯了大量错误,偶尔我们很幸运:我们关心的指标改进到足以改变生产线,将我们偶尔成功的结果具体化到新的生产过程中,最终将您的机器放在您面前。
唯一重要的是,实际硬件上的实际程序会看到相关指标(如总拥有成本、响应速度等)的真正改善…。如果一个指标对人没有帮助,这只是一种虚荣的追求,可能会因为投资不足而使重要的指标变得更糟。
通过做出考虑可用数据的决策,我们能够破除如此多的废话。我们有太多的想法,我们的经验告诉我们应该促使我们的代码改进,但当我们实际测量时,这根本不是真的。使您的Java、C++、Haskell或JS程序变得更快的代码更改可能不会使Rust变得更快。不要仅仅因为您在90年代编写的某个程序在手动循环展开时工作得更好,就让铁锈代码变得难看。编译器会发生变化,在很多方面都会变得更好。
别像个男子汉一样。在手头有真实数据的情况下做出决策,限制傲慢的危害。
因此,性能关键型工程的许多方面都可能与环境高度相关,并且在不同的机器上可能会有很大差异。它有助于避免动词“to be”及其词缀“is”、“are”等…。在描述观察结果时。
当我们说某事“is”是另外一件事时,为了方便起见,我们随意地将两个相似的东西等同起来,在某种程度上我们是在撒谎。避免错误的等值(通常通过使用“is”、“…”等词很容易辨认出来)。我们都可以更精确地沟通,我们可以让自己更有效地对复杂性进行推理。使用“将要成为”的短语描述时看起来完全模棱两可的情况,在注意避免错误的对等时往往是相当明确的。这种被称为E-Prime的一般形式的速成。
不要说“无锁队列比互斥锁支持的队列更快”,比如“在硬件H上,T个线程在紧循环中运行执行操作O,我们特定的无锁队列的延迟分布是X1,我们特定的互斥锁支持队列的延迟分布是X2。”这句话的意思是“在硬件H上,T个线程运行在紧循环中执行操作O,我们特定的无锁队列的延迟分布是X1,我们特定的互斥锁支持的队列的延迟分布是X2。”考虑到某些硬件、争用和许多其他因素,您的无锁队列有时可能会比构造良好的互斥队列性能更差。“表现更差”到底是什么意思?不同的用例将具有不同的可用资源组合。与不需要分配或执行RCU的互斥结构相比,在高争用情况下吞吐量更高的特定无锁实现可能会使用更多内存,具有较慢的中值延迟,更麻烦,创建时间也更长。不管怎样,这会是高争用吗?你确定你写这篇文章是为了你的系统,还是因为你想在工作中表现得更有男子气概?
当我们谈论比较指标时,同样重要的是要避免说经常被误解的东西,比如“工作负载A比工作负载B慢15%”。与其说“更快”,不如用延迟或吞吐量来描述,因为两者都可以用来描述“速度”,但它们是完全相反的。用相对百分比来表述往往会产生误导。如果我们不知道A(90)的实际值,那么A(90)比B(100)低10%意味着什么?很多人会认为B是1.1*A,但在本例中,1.1*90=99。一般来说,用比率而不是相对百分比来描述比较衡量更好。
短语Workload A比Workload B慢20%可以更清楚地表述,因为根据测量,Workload A的吞吐量是Workload B的4:5。即使许多人会看到这一点,并立即在他们的脑海中将其翻译为“80%”,但不正确推理差异的可能性较低。
当我们的交流变得不那么模棱两可时,我们就有更多的大脑周期,可以基于清晰的心理模型来加快速度。我们等待的时间可以更好地利用,因为它可以更精确地定向。
性能指标有多种形式和大小。工作负载将有一些比其他指标重要得多的指标。每个工作负载都有自己的一组优先级和可用资源。
在这一点上,我有义务猛烈抨击基准营销,但老实说,它通常是项目取得成功的重要工具-你只需要清楚你的实际衡量标准是什么。不要欺骗别人,给人们复制你的发现的方法。
利用率-系统(服务器、磁盘、哈希图等…)。正忙于处理请求,而不是等待下一个请求到达。
饱和度-系统处理请求之前必须排队的程度,通常以队列深度(长度)衡量。
快速搁置-从2020年起,云提供商倾向于低价计算(ec2、lambda等…)。以增加您对其更昂贵的存储产品的依赖。“数据引力”的概念存在于您的计算必须停留在存储所在的位置。如果您的数据停留在旧的云提供商中,则不能将您的计算移动到不同的云提供商。而出境交通的税收也很高,这使得外出变得更加痛苦。在你的计算机存在的地方,可能会产生更多的数据,从而增加引力场。请确保您对某处基础设施的原始计算主要考虑了存储成本。工程师喜欢使用像Spanner这样奇特的托管数据库,但存储的每字节成本是天文数字。请注意。
在尝试确定我需要支付多少台服务器才能完成任务时,我们需要考虑延迟、吞吐量和所需的空间(内存和存储)。
延迟和吞吐量方面的考虑通常是相互直接矛盾的。如果我们想要优化服务器的吞吐量,我们想要增加这样的机会,即当服务器完成处理一个请求时,它已经有另一个请求排队并准备开始。100%的利用率意味着服务器始终在做有用的工作。如果在前一项完成时没有等待服务的工作,则利用率会随吞吐量一起下降。让事物在队列中等待是增加吞吐量的一种常见方式。
但是等待(饱和)不利于延迟。在其他条件不变的情况下,向系统发送更多的请求将导致延迟,因为请求在得到服务之前必须排队等待的风险也会增加。如果我们想要最小化服务器的延迟,我们想要增加进入它的空队列的可能性,因为在该队列中等待会降低每个请求的速度。
延迟与吞吐量是一个基本关系,它对性能敏感型工程有着巨大的影响。我们经常面临这样的抉择:我们是否希望我们的请求快速,或者我们是否希望系统通常每秒处理许多请求,其中一些请求由于在队列中等待太长时间而处于停顿状态。
如果您想要同时改善延迟和吞吐量,您需要使工作单元的执行成本更低。
不同的系统在利用率和饱和度之间会有不同的关系。网络适配器通常被设计为能够持续接收越来越多的工作并避免饱和,直到相对较高的利用率。其他设备(如旋转磁盘)将很快开始饱和,因为工作会导致其他工作变慢,因为在处理请求之前,需要将磁盘轴拖动到另一个物理位置。在这里,智能调度可以极大地改变利用率和饱和之间的关系。大多数基于闪存的现代存储设备本质上是分布式数据库,其中每32MB是一个不同的碎片,因此通过保持队列深度相当深,您可以获得大量吞吐量而不会立即出现负饱和,因此设备中更多的分布式芯片可以同时工作。
一个重要的见解是,如果您有一个想要优化以获得低延迟的系统,那么如果它所依赖的子系统也针对延迟进行了优化,这将是很有帮助的。如果您正在为希望最小化响应时间的用户请求提供服务,那么您可能希望避免让响应依赖于针对批处理性能而不是低延迟进行调优的分析数据库。如果您希望在较低的时间内处理数万亿条记录,而不太关心处理一条特定记录的时间,那么您可能希望依赖那些以延迟为代价调优吞吐量的系统。
所有系统都有特定的延迟-吞吐量权衡。当您的系统依赖于关键路径中的子系统时,其中进行了不同的延迟-吞吐量权衡,您的整个系统将表现得更糟。如果您强制延迟受限用户响应在其Web浏览器加载结果之前通过Kafka队列,则在延迟受限工作负载的关键路径中引入吞吐量受限依赖,结果将是更糟糕的体验。我们应该在低延迟工作负载的关键路径中使用低延迟依赖关系。我们应该在高吞吐量工作负载的关键路径中使用高吞吐量依赖项。在我们的关键路径中混合和匹配系统,而不评估它们的排队特性,可能会导致糟糕的性能。
测量正在发生的事情,并使您的排队行为保持一致,您的系统就会飞起来。
在负载较轻的情况下,吞吐量受限系统有时可以缩小规模以减少缓冲,并在较低负载下实现较低的延迟。AndreaLattuada向我提到了自动调优系统的一个很好的例子:面向吞吐量的系统可以接收全部新请求,对它们进行批处理,并且通常在可能的情况下继续选择队列长度作为批大小。当有少量请求进入时,这会自动将批处理大小调整为低(减少延迟)。当请求量较少时,这会产生不错的低延迟请求,但会使系统向上扩展,并随着请求速率的增加而利用批处理优化。随着负载的增加,延迟会受到影响,因为在这种情况下,延迟不是很高的优先级。
自动调优低延迟系统如果要保持低延迟,则必须在吞吐量增加时扩展队列长度以外的其他内容。低延迟取决于将水中等待的时间量保持在所需阈值以下。因此,我们必须通过使用更多内核、更多服务器等…来增加并行量,而不是增加批处理大小。然而,正如Amdahl定律和USL所表明的那样,我们只能按一定的量并行化程序,而并行化的行为本身就会带来额外的成本,这可能会抵消并行化带来的任何可能的收益。
在构建系统时,重要的是要了解您的目标是将请求处理保持在低延迟还是高吞吐量,因为这将对您的设计、依赖性、伸缩技术等…产生巨大影响。
“系统性能:企业与云”(Brendan Gregg著)(购买此书只是为了阅读第2章:方法论)。
在满足某种要求的过程中,一个系统通常会依赖于其他系统。数据库依靠内核将其线程调度到CPU上,并提供对存储的访问。我们可以通过了解是什么导致数据库的子组件和与其他系统的交互变慢来了解是什么导致数据库变慢。
如果您正在测量大量请求的延迟,有许多方法可以从测量中获得意义。人们常常依靠平均数来理解大量的测量结果。但是对于我们高度离散的计算机系统来说,平均值并不是很有趣,因为它隐藏了异常值的影响,并且不能让我们深入了解数据的分布。像正态曲线和t-检验这样的东西不适用于不服从正态分布的数据。确定我们的分发到底是什么样子是我们工作的重要部分。
我们通常使用直方图,这样我们就可以了解数据的分布情况。第0个百分位数是最小度量值。第100个百分位数是最大度量值。第50个百分位数是中位数,所有测量值的一半低于中位数,一半高于中位数。90%是所有测量的延迟中90%小于经常的延迟。通过使用对数分组法索引到大小在真实观测值的1%以内的桶数组来测量直方图是相当便宜的。历史学家的板条箱是从雪橇中提取出来的,以一种超级便宜的方式帮助进行这些测量。
后端系统的延迟分布是稳定的1ms,直到第99个百分位数,在那里它跳到1s。
前端系统必须等待最慢的响应,然后才能响应用户
单个请求需要等待1秒的概率为1%(99%为1s)。假设分布是独立的,2个请求需要等待1秒的概率是1.9%(1-(0.99^2))。直觉:如果我们发送1,000,000个请求,百分比不会变成1,000,000*1%或10,000%,因为100%是事件的最大概率。在本例中,第99到第100个百分位数之间的所有内容都恰好是1秒。我们所有最慢的1%请求都需要1秒。
100个请求需要等待1秒的概率为1-(0.99^100),或63%。即使事件只有1%的几率发生,但在63%的情况下,我们的前端系统将不得不等待1秒,因为需要。
.