“良好的性能评估能够深入理解系统的行为,不仅量化整体行为,还能量化其内部机制和策略。它解释了系统为何以这种方式运行,是什么限制了这种行为,以及必须解决哪些问题才能改进系统。”10
随着软件从桌面端转移到数据中心,以及手机应用将服务器请求作为另一半,用于大规模分布式事务系统的观察工具并没有跟上环境的复杂性。探索更简单的环境可以帮助揭示当今工具用户和工具构建者面临的一些问题。从仔细观察一个程序及其完整的周围环境,甚至像“Hello, World!”这样简单的程序中,也能学到很多东西。本文将通过六种不同的视角来审视“Hello, World!”的执行过程,以了解当今工具中经常缺失的内容;同样的分析也适用于复杂的数据中心软件。工具设计者和工具用户必须坚持填补这些空白。如果你看不到问题,你就无法解决它。
服务提供商常常需要遵守性能承诺(“99% 的网页请求将在 800 毫秒内完成服务……”),但却很少有工具来衡量滞后事务的存在,更不用说理解它们的根本原因了。这样的承诺是建立在沙滩上的。
图 1 显示了一个耗时 160 毫秒的 Web 搜索查询的一部分,显示在顶行,下面显示了它使用的远程服务的局部调用树(大约在 2005 年)。实际上,该查询分布在约 2000 台服务器上,每台服务器执行部分搜索。该图显示了顶层 RPC(远程过程调用)树的一部分,工作分布在 x 轴的时间上和 y 轴的不同服务器上。在左上角的初步调用之后,有 93 个并行调用在 93 台不同的服务器上进行子搜索(并非全部显示)。这些调用返回的时间各不相同,从大约 30 毫秒到 120 毫秒不等。其中一个调用的时间比其余调用长约四倍。顶层服务器执行的代码很少,主要是在等待这个落后者完成。如果不观察这些跨服务器的动态,我们就不知道为什么整个查询需要这么长时间。但即使有了 Dapper12 及其类似工具等优秀的 RPC 工具,问题仍然存在:为什么一台服务器在执行 CPU 密集型工作时,会比其他执行几乎相同工作的服务器慢四倍。
为什么这很重要?因为大多数事务系统通过并行执行许多子操作来提供表面的速度,并在所有子操作完成后返回总体结果。在大约 100:1 的并行扇出比率下,这意味着几乎所有顶层搜索都以第 99 百分位数最慢的子搜索的速度运行。这就是它重要的原因。在每秒 1,000 个事务的情况下,(根据定义)每秒有 10 个事务超过第 99 百分位数的响应时间。可以使用哪些工具来查找此类慢响应的根本原因?
正如 Sun Microsystems 的 Bryan Cantrill1 在 10 多年前观察到的那样,观察方式发生了两次深刻的转变:从开发到生产,以及从程序到系统。观察一个运行单个程序的空开发系统几乎没有启发意义。今天,我们必须观察同时运行数十个程序的实时生产系统,才能理解它们的真实性能和底层的软件动态。但是,我们必须使用能够显示足够信息的工具,以便在那种环境中使用——特别是,能够帮助揭示为什么那个滞后的服务器响应比其他服务器慢四倍的工具。传统基准测试程序的设计使其具有可重复的性能,并且可以孤立地进行研究。在线事务,即使是重复相同的事务,也会发生变化,并且不具有可重复的性能。用于基准测试的观察工具通常对事务不太有用。
可以通过探索所有程序中最简单的 helloworld.c 来展示不良工具的问题,并缓慢地揭示大多数当今性能工具中不可见的底层动态。谁会费心去基准测试“Hello, World!”?请先放下你认为这太微不足道而无趣的怀疑态度。
程序员的脑海中对他们的程序正在做什么有一个概念图。这些概念图过于简单,而且基本上总是错误的。有时,他们正在寻找的性能问题存在于缺失的部分,或者存在于“事件 A 导致事件 B”的不可见动态中,而时间顺序和因果关系根本没有被观察到。
尤其“有趣”的滞后事务是那些在负载重的生产系统上耗时过长,但在开发系统或几乎空闲的生产系统上却不会的事务。在单台服务器上,此类滞后可能是由来自其他事务或其他程序的复杂交互和干扰引起的。5 无法预测哪些事务会遇到干扰,并且它们在重复运行时通常会全速运行。在这种环境中的响应时间直方图显示了不频繁的慢响应的长尾,第 99 百分位数的响应时间可能是平均响应时间的 10 倍,如图 2 所示。该图是来自网络磁盘服务器程序的单个磁盘读取的延迟(响应时间)直方图(大约在 2012 年)。中位数为 26 毫秒,但第 99 百分位数延迟为 696 毫秒——慢了 25 倍。(图中的时间刻度在 100 毫秒处和 1,000 毫秒处分别变化了 10 倍)。Google 的 Jeffrey Dean 和 Luiz Andre Barroso2 更详细地讨论了数据中心尾部延迟。
在探索可能的性能观察工具时,重要的是要了解工具的视野中缺少什么。缺失太多意味着不了解情况的猜测或方向错误的努力。如果你能看到每个 CPU 内核每纳秒在做什么,你就能深入了解在单台服务器上运行的软件。然后你可以绘制小图,如图 3 所示,显示单台服务器多个内核上的运行时间。图 3 显示了单个 CPU 内核 #2 上的活动,运行时间从左到右,细黑线显示在空闲循环中花费的纳秒,半高矩形显示用户模式执行时间,全高矩形显示内核模式执行时间。时间以灰色显示在顶部,彩色显示在底部。
彩色内核模式全高区域具有浅蓝色/绿色/红色背景和黑色/灰色/无外边缘,分别表示中断请求 (IRQ)、调度程序和系统调用以及页面错误。内核前景中有两种颜色的条纹,允许通过中断/系统调用/错误号区分约 250 种组合。用户模式半高区域也有两种颜色的条纹,允许按进程 ID 号区分程序。水平条纹对的颜色没有特别的含义,只是对于每个系统或进程号来说是独特的。条纹的垂直宽度和位置也没有特别的含义;它们的左右长度是有意义的,显示了运行时间。
这些图表显示了单个 CPU 内核上发生的一切。每一纳秒都被覆盖,没有任何遗漏。“没有遗漏”的观察概念至关重要。如果缺少部分,你就无法对正在发生的事情做出有力的陈述,特别是无法对没有发生的事情做出有力的陈述。
这是 helloworld.c6,其中用两个额外的 kutrace::mark_a
语句进行了修饰,以标记 main 程序第一行和最后一行的执行时刻
int main(int argc, const char** argv) {
kutrace::mark_a("Hello");
printf("hello world\n");
kutrace::mark_a("/Hello");
return 0;
}
可以使用各种工具观察到这个最简单程序的执行情况吗?在你的脑海中画一个图,想象一下 helloworld 的图 3 的样子,然后继续阅读。
深入研究连续的观察细节级别将说明你的脑海中的图可能缺少什么。这些图显示了 helloworld 在 Linux 操作系统下,在四核 x86 处理器上运行的情况。在 Windows 下或在不同的处理器上运行会得到类似的结果。
当今的许多观察工具可以观察单个程序中用户模式代码的一些信息。图 4 是 helloworld 中 main 程序用户模式代码的小图。它在 CPU #1 上运行,其半高用户模式矩形使用基于 PID(进程 ID)的一对颜色绘制。运行时间约为 40 微秒。方框标签 Hello
和 /Hello
标记了 main 程序的第一行和最后一行的执行,由 kutrace::mark_a
语句生成。
(平移和缩放此图像以匹配各图的 x 轴时间刻度,并以全矢量分辨率查看它们。Shift-单击任何时间轴项目以使用其名称和详细信息进行注释。)
CPU 分析工具(如 gprof4、Visual Studio Profiling Tools8 或 VTune7)将按子例程名称,或者按行号或调用堆栈,提供 main 程序 CPU 时间的细分,但没有时间顺序。当然,对于只有一行代码的 helloworld 来说,这相当无趣,但分析对于识别或总结批量程序中时间花费在哪里非常宝贵。它们对于理解分布式事务不太有用。
有些程序有多个线程,但许多观察工具只能观察第一个线程,或者它们可能会观察所有线程,但将结果合并到一个假装的单线程中。这两种设计都会隐藏信息——它们让你成为所谓的路灯效应9 的受害者,在路灯下寻找,而不是在丢失钥匙的地方寻找。
仔细查看图 4 中的图,你会注意到在 main 程序第一行的 Hello 标记之前有一点用户模式执行。你还会注意到一些空白——空白区域显示时间在流逝,但没有用户模式代码在执行(空闲循环也没有执行)。你脑海中的图通常会跳过这种细节。
用户模式工具将这些空白中的任何时间都显示为属于其后第一个用户模式代码片段。因此,无论空白区域中有什么,都会被归咎于一些方便的附近用户代码,从而扭曲用户代码分析并隐藏空白。空白区域代表了先前显示的 40 微秒中的 10 微秒。请继续阅读。
图 4 中缺少的一件事是在 main()
的第一行之前和最后一行之后花费的执行时间。你可能会认为这无关紧要。
当我在 Adobe 工作时,我们遇到了一个谜,早期版本的 Acrobat 的启动时间为 8 到 10 秒。用户模式 main 程序工具没有揭示问题。它们对此视而不见。我们最终构建了一个内部工具,可以覆盖所有 Acrobat 代码。它立即揭示了隐藏在 C++ 构造函数中的时间,其中许多构造函数在 main()
的第一行之前运行;这些构造函数花费了近 7 秒。将这些构造函数中的工作移到一个单独的非阻塞线程中,极大地缩短了应用程序的启动时间。这个持续数月的谜团通过一个工具的第一个输出就解决了,这个工具实际上可以观察到问题。修复大约花了 20 分钟。
记住,如果你看不到问题,你就无法解决它。
图 5 显示了 helloworld 的完整用户模式执行,包括所有启动和关闭时间。图 5 是图 4 的超集,显示了四个 CPU 中的两个 CPU 上的所有 helloworld 用户模式活动,包括 main()
之前和之后的时间。运行时间约为 400 微秒。方框标签 Hello
和 /Hello
标记了 main 程序的第一行和最后一行的执行,如图 4 所示。
该图揭示了 helloworld 进程实际上是在 CPU #3 上开始执行的,然后被操作系统迁移到 CPU #1(它没有揭示原因)。所有这些 CPU 时间都在做什么?它被用于加载和初始化程序。当我们运行程序时,我们很少考虑程序来自哪里。在本例中,它来自 RAM 中的文件系统缓存(因为我之前运行过该程序;运行时间太短,不可能来自磁盘;使用空文件系统缓存运行会在前面增加约 11 毫秒的等待磁盘活动的时间)。
比预期更多的 CPU 时间用于解析 .exe 映像、重定位映像的指令、分配和初始化运行时堆栈、获取 stdin/stderr/stdout 打开并可用等。在 main 程序退出后也花费了一些时间。我们稍后会讨论这个问题。
你可能遇到过每五分钟运行一次的数据中心程序或命令行脚本(Perl、Python、JavaScript)。或者,在数据库环境中,每个查询在访问存储设备之前都会被解析和优化。对于长时间执行来说,在启动或关闭时花费几秒钟并不重要,但对于短时间执行来说却很重要。每五分钟启动三秒钟占总时间的百分之一。十个这样的程序/脚本可能会消耗总时间的 10%。启动时间的一个显着特征是,它是相对于最终的实际工作串行化的,因此它破坏了任何后续的并行化。Christina Delimitrou 和 Christos Kozyrakis3 探讨了串行化和尾部延迟在几个级别上的关系。
现在,关于那些空白。它们是在内核模式执行中花费的时间(图 6)。这与图 5 的 400 微秒相同,但明确显示了内核模式活动。大部分时间都花在了处理页面错误上。内核时间以全高矩形显示。
高高的粉红色背景矩形是页面错误处理,绿色的矩形是系统调用,两个带有蓝色背景的矩形(约 180 微秒,约 320 微秒)是中断。内核代码中的时间比用户代码中的时间更多。
性能分析工具不太常显示内核时间的详细信息。大多数工具对此视而不见。现在你可以看到它们了,你可能会问,是什么导致了所有这些页面错误?事实证明,程序加载通常会将可执行磁盘映像映射到虚拟内存中,然后使用页面错误将该映像的页面从磁盘传输到物理内存中。(如果映像已在 RAM 中的文件系统缓存中,则这些传输只是从内存复制到内存,但仍然占用大量时间。)这些页面错误的延迟可能会在相当晚的时候发生——当程序的不常用部分首次使用时——花费了意想不到的毫秒级磁盘访问时间,并且可能损害某些事务的延迟。
在 main()
的第一行附近也有页面错误。程序的运行时堆是由 brk()
系统调用设置的,该调用将地址空间扩展到加载程序的正上方。一种常见的实现方式是为所有新分配的堆页面构建页表,但将它们都指向单个内核全零页面,并将它们标记为写时复制。这节省了将所有新堆页面归零的时间,或者更准确地说,延迟了该时间。每当堆页面首次写入时,就会发生页面错误;内核分配一个真实的物理页面并将所有零复制到其中,将页面标记为读写,然后返回。
图 7 更详细地放大了 main()
,以查看各个时间跨度并填补图 4 的空白。这是图 6 在 helloworld 的用户模式加内核模式活动上的放大图。运行时间约为 50 微秒。最左边是四个页面错误,然后是对 fstat()
的短暂 330 纳秒调用,然后是以太网中断,一对未标记的对 brk()
的调用,一个页面错误,对 12 字节“hello world<cr>,
”的 write()
调用,另一个页面错误,然后是对 exit_group()
的系统调用。
前四个页面错误是堆分配的尾声。fstat
调用不在 C 源代码中,而是隐藏在 C 运行时库中。它正在检查 stdout
在写入之前是否已打开。在 336 微秒时对 brk()
的调用以及随后的页面错误是什么?Printf 在堆上分配了一些东西,因此堆需要在 C 运行时库中扩展。很少有工具能够揭示每次 malloc()
之后可能发生的页面错误和归零,包括系统库中的那些。
在最右边,对 exit_group
的调用花费了 5.5 微秒来结束程序,这是一个相对较短的关闭序列。
但是等等!还有更多。
到目前为止,我们已经查看了所有 helloworld,但仍然看不到整个计算机上正在发生的其他事情。几个不相关的中断占用了 CPU 时间,但回想一下,滞后的数据中心事务可能是由来自其他事务或其他程序的干扰引起的。能够观察单个程序的工具看不到其他程序的存在和影响。
同一台服务器上还在运行什么?图 8 显示了在这个小型四 CPU 服务器上运行的所有程序:helloworld 加上所有其他正在运行的程序。CPU #0 完全空闲,CPU #2 正在运行一个 CPU 密集型批处理程序;其他程序在 CPU #1 和 #3 上运行。运行时间约为 800 微秒。图 6 是从中间的 400 微秒绘制的。这是“没有遗漏”的图,显示了每个 CPU 内核的每一纳秒。
helloworld 程序实际上是通过来自远程机器的 ssh(安全外壳)运行的。在 CPU #1 左上角是一个以太网中断,带来了传入的命令行 ./helloworld
,然后是 sshd(ssh 守护进程)程序,通过 CPU #3 上的内核线程 kworker0 将其转发到命令行处理器 bash.2。Linux bash shell 的这个实例在 CPU #1 上大约在 900 微秒时开始克隆自身,在 CPU #3 上创建 hello。迁移程序将其移动到 CPU #1。(这是图 5 中提到的缺失原因;当核心空闲时,Linux 优先在执行 clone()
系统调用的 CPU 内核上执行克隆程序。)在 hello 退出后,有一些未标记的 sshd 活动用于将结果传回,然后 bash.2 再次在 CPU #3 上运行,以完成关闭 helloworld 并发送 bash 的下一个远程命令提示符。在整个过程中,一个不相关的程序 memhog_1 正在 CPU #2 上运行,使其核心的一级数据缓存崩溃,并标记其外循环的每四次迭代。CPU #0 恰好在整个显示时间内都处于空闲状态,但实际上在该图之外正在接收定时器中断。
只有使用能够观察时间顺序的工具,你才能看到因果关系。只有使用“没有遗漏”的图,你才能注意到周期性后台任务会导致面向用户的事务的周期性减速。只有使用能够观察服务器上所有运行程序的工具,你才能希望理解串行化和干扰的来源。最后,只有使用精细的微秒级观察,你才能看到这种干扰的动态。
在示例中,sshd/kworker/bash/helloworld 程序之间发生了许多串行化的交互,每个程序都在等待来自另一个程序的工作,但没有看到太多的跨程序干扰。尽管如此,还是存在一些干扰。在大型分布式事务系统中,串行化交互和干扰很普遍。
看看红色的正弦波。这些是合成指示,表明这个特定的 x86 CPU 从其省电深度睡眠 C6 状态中恢复过来需要多长时间——大约 30 微秒。这可能会给唤醒睡眠任务的时间增加 30 微秒。如果一些复杂的软件通过在数十个线程之间依次传递工作来取得进展,每个线程在等待工作时都处于睡眠状态,那么额外的延迟就会累积起来。这可能意味着空闲服务器上的事务比不经常睡眠的合理负载服务器上的事务更慢。它还可能破坏 RDMA(远程直接内存访问)协议的响应时间,这些协议依赖于微秒级的网络中断响应。
在图 7 中的 316.5 微秒时,一个以太网中断减慢了(从 helloworld 那里夺走了 CPU 时间)。如果一个 Web 服务器程序恰好在一个处理大量磁盘中断的 CPU 内核上运行,并且磁盘非常繁忙,会产生什么影响?或者在一个处理大量网络中断且网络非常繁忙的 CPU 内核上运行?该程序默默地遭受着许多工具无法观察到的影响。
我曾经在 Google 的一台磁盘服务器机箱上工作过,并且将 CPU 的计算能力低估了 25%。为了确定系统的大小,我使用了来自内部核算工具的测量结果,该工具对各种用户模式程序中花费的总 CPU 时间以及代表它们运行的内核模式代码进行收费。可悲的是,我没有意识到核算工具不知道应该向哪个程序收取传入网络数据包的中断处理费用。传出数据包向发送者收费,但对于传入数据包,接收者在处理完数据包后才知道。核算程序只是丢弃了传入的网络中断时间,这是一个根本的设计缺陷。它应该简单地为“传入网络”甚至“其他”设置一个类别,并设计成将所有时间都分配给某个类别,“没有遗漏”。当然,假设是所涉及的时间很短,可以忽略不计。(请参阅侧边栏。)
看不见的现实是,一个完整的 CPU 内核被繁重的网络流量的中断处理所消耗。这在 32 核计算服务器上并不重要,但在小型四核磁盘服务器上却造成了 25% 的差异。(该项目得以恢复,仅仅是因为磁盘服务器软件人员花了两个痛苦的月时间,使该软件速度提高了 30%。)
在复杂的软件中,认为所涉及的时间很短并且可以忽略不计的假设是致命的。我曾在 Gmail 工作过一段时间,在正常的开发环境中,使用离线负载测试来运行一堆虚假的邮件事务,并衡量当前版本是否比以前的版本更快或更慢。当然,罕见的三级事务不在负载测试中,因为所涉及的时间“很短并且可以忽略不计”——直到我们找到一个拥有 100,000 封邮件消息的用户和一个离线程序,该程序使用三级 IMAP(Internet 消息访问协议)接口访问它们,并且该程序在邮件消息数量中存在 N**2 错误,这导致每次访问都会触发一个 Gmail 错误,该错误每次用户登录时都会占用 20 分钟的服务器 CPU 时间。花费了太长时间才找到一个用户和未经测试的 Gmail 路径,然后修复它,并将 IMAP 事务添加到负载测试中。
现在看看图 8 中的空闲时间。更具体地说,看看所有退出空闲状态的转换。这些是什么?每次退出空闲状态的转换从根本上都代表着某个程序正在等待某个事件;该事件最终发生,因此程序再次变得可运行。关键信息是在核心退出空闲状态之前(可能在不同的 CPU 上)发生了什么。那里的活动——磁盘中断、定时器中断、释放争用的软件锁等等——表明程序正在等待什么。在单个服务器中,某些滞后事务的延迟不是来自 CPU 干扰本身,而是来自等待其他程序或同一程序的其他线程。
在这种环境中,你需要粗略的工具,可以识别每次等待的原因,而不仅仅是等待了多少秒的未知事件。然后你可以看到导致等待时间过长的意外程序动态(事件 A 导致事件 B)。也许应该跨 16 个内核并行完成的 163 个事务突发实际上是在单个内核上顺序运行的,使得“最后一个人”的等待时间比程序员脑海中的图长 10 倍以上。我以前生活中的真实事件。
然而,有时,某些事务的 CPU 密集型部分正在执行,但处理速度异常缓慢。
图 9a 和 9b 更详细地放大了某些页面错误,但此外,它们还显示了每个时间跨度中花费的 IPC(每周期指令数)。这不是几秒钟内的粗略平均值;它是对显示的每个微秒级时间跨度的精细测量——正是十几年前提倡的那种测量11。在峰值时,我们的芯片可以执行 4 个 IPC。低 IPC 意味着大部分执行槽都被浪费了。对于图 9a 中的第一个页面错误,标记为 266.51us page_fault; 1.95us IPC=0.625
,时间跨度为 1.95 微秒,或者在该特定机器上约为 6,000 个周期。在此期间,CPU #1 执行了约 3,700 条指令,因此 3,700/6,000 = 0.625 IPC。这里有关于干扰的大量信息。图 8 中的一些页面错误和其他活动的 IPC 也显示出来了。用户模式代码由于内核模式缓存干扰而减速,然后恢复。
在图 9a 中,在 CPU #1 上大约 259 微秒处的 helloworld 用户代码上的小三角形的角度表明 hello 执行了大约 0.75 IPC。现在看看第一个页面错误之后的一小段 hello 代码。它的 IPC 下降到 0.125 以下。事实上,在每个页面错误之后,hello 都以低 IPC 缓慢运行,然后在右侧的 mprotect
调用之后再次加速。你正在直接看到页面错误内核代码破坏 helloworld 正在使用的缓存的速度后果。
图 9b 展示了相反的效果。内核模式代码由于用户模式缓存干扰,在冷缓存状态下启动缓慢,然后速度加快。请注意 CPU #1 上首次页错误的低 IPC,然后注意后续页错误时页错误代码的 IPC 上升。它变得更快是因为更多内容在缓存中。正如在图 9a 中一样,用户程序由于缓存中的内容减少而变得更慢。另请注意,在这小段时间间隔内,页错误处理程序中的 IPC 比用户代码中的 IPC 高得多。传统观点认为,内核代码总是在冷缓存状态下执行,因此本质上很慢。但当你可以实际观察到真实的 CPU 行为时,就会发现这是错误的。
对于理解 SPEC(标准性能评估公司)基准测试和其他批处理程序来说,提供细粒度观察结果但会使被观察程序减速 20 倍的工具是可以接受的。但在生产数据中心中,它毫无用处。即使像 tcpdump 造成的百分之七的减速,在面向用户的实时数据中心一天中最繁忙的时段也是不可接受的。当然,更有趣的尾部延迟问题只发生在面向用户的实时数据中心一天中最繁忙的时段。该怎么办?
只有 CPU 开销为百分之一或更低的细粒度观察工具才可用于实时用户流量。这意味着工具设计者和工具用户必须测量每个工具的开销,并坚持使其微小。本文中的图表是我使用一个软件工具13生成的,该工具经过精心设计,在每个 CPU 核心每秒跟踪 200,000 个内核-用户转换事件时,开销为 ¼%,而在同时跟踪 IPC 时(由于读取 x86 指令-retired 性能计数器的速度非常慢,然后是缓慢的除法运算),开销不到百分之一。
图 10 中的表格给出了 CPU 观察工具的简短列表(其他工具可以观察磁盘和网络流量,这里不讨论)。这些性能观察工具都可以通过在网上搜索“<name> linux”或“<name> windows”找到。小写名称是 Linux 工具,大写名称是 Windows 工具。
采样工具定期检查 CPU 程序计数器,以按子例程或行号给出 CPU 资源消耗大户的概览;它们通常需要从源代码重新编译才能有效使用。计数工具定期检查各种硬件和软件计数器,无论是针对单个程序还是服务器上运行的所有程序。由于它们执行频率不高,采样和计数工具的开销相当低,因此可以在实时数据中心机器上运行。跟踪工具记录时间顺序事件,无论是针对单个程序还是服务器上运行的所有程序。它们通常会使执行速度减慢约 30 倍,这使得它们完全不适合实时数据中心机器。KUTrace 是一个例外,用于生成图 3-9 的工具,其开销不到百分之一。跟踪器是唯一可以直接揭示跨线程和跨程序干扰的工具。只有完整跟踪才具有“不遗漏任何内容”的特性。
从仔细观察程序及其完整的周围环境(即使是像 helloworld 这样“微不足道”的程序)中,可以学到很多东西。以下某些或全部内容可能在你脑海中的画面中缺失了
• 用户模式执行间隙
• 启动/关闭时间
• 页错误时间、中断时间、系统调用时间
• 内核代码中花费的时间比用户代码中花费的时间多
• 延迟调入很少使用的代码页
• 来自 C 运行时库中的 fstat()
, malloc()
• 每次 malloc()
后可能发生的页错误和清零
• 从省电睡眠状态恢复
• 串行等待他人;根本不执行
• 缓存干扰
如果将 helloworld 替换为重要的数据库中心程序,您可以更多地了解该程序及其与单台服务器上干扰进程的交互。在 Google 找到并追踪到一个这样的干扰影响,就为我支付了 10 年的工资,这是指 20 分钟修复后节省的资金而言。
随着越来越多的软件从桌面转移到数据中心,以及越来越多的手机使用服务器请求作为应用程序的另一半,用于大型分布式事务系统的观察工具并没有跟上步伐。这使得人们倾向于使用更简单的工具在路灯下寻找。当您突然遇到复杂的性能危机时,沿着这条路走下去会浪费大量高压时间。
相反,要了解您使用的每个工具的盲点,了解您需要哪些信息来理解性能问题,然后寻找可以实际直接观察该信息的工具——并以足够低的开销和失真来保持其有用性。
您现在可以继续保持怀疑态度了。
感谢 Lance Berc、V. Bruce Hunt、Amer Diwan、Mark Hill、Michael Brown 和 审稿人为实质性地改进本文草案所做的贡献。
1. Cantrill, B. 2006. 显而易见却被忽略;acmqueue 4 (1), 26-36. https://queue.org.cn/detail.cfm?id=1117401
2. Dean, J., Barroso, L. A. 2013. 大规模尾部延迟。《 通讯》56 (2), 74-80.
3. Delimitrou, C., Kozyrakis, C. 2018. 阿姆达尔定律与尾部延迟。《 通讯》61 (8), 65-72.
4. Graham, S. L., et al. 1982. gprof:调用图执行分析器。载于《1982 年 SIGPLAN 编译器构造研讨会论文集,SIGPLAN 通知》17 (6), 120-126; https://docs.freebsd.ac.cn/44doc/psd/18.gprof/paper.pdf.
5. Gregg, B. 2012. 有条不紊地思考性能;acmqueue 10 (12). https://queue.org.cn/detail.cfm?id=2413037
6. Kernighan, B. W. 1974. C 语言程序设计:教程;https://www.lysator.liu.se/c/bwk-tutor.html.
7. Intel. 2018. 现代处理器性能分析。英特尔开发者专区;https://software.intel.com/en-us/intel-vtune-amplifier-xe.
8. Microsoft. 2015. 性能分析初学者指南。微软开发者网络;https://docs.microsoft.com/en-us/visualstudio/profiling/beginners-guide-to-performance-profiling?view=vs-2015.
9 Norton, R. A. 2010 路灯效应;https://en.wikipedia.org/wiki/Streetlight_effect.
10. Ousterhout, J. 2018. 始终深入一层测量。《 通讯》61 (7), 74-83
11. Purdy, M. 2006. 现代性能监控。acmqueue 4 (1), 48-57. https://queue.org.cn/detail.cfm?id=1117404
12. Sigelman, B. H., et. al. 2010. Dapper,大型分布式系统跟踪基础设施。谷歌 AI;http://research.google.com/pubs/pub36356.html.
13. Sites, R. 2017. KUTrace:纳秒都去哪儿了?在捷克共和国布拉格Tracing Summit 峰会上发表的演讲;https://tracingsummit.org/w/images/3/30/TS17-kutrace.pdf.
网络应用程序是交互式的
网络时代需要新的模型,用交互代替算法。
- Antony Alappatt
https://queue.org.cn/detail.cfm?id=3145628
先生,请远离 ASR-33!
为了推动编程语言的发展,我们需要摆脱 ASCII 的束缚。
- Poul-Henning Kamp
https://queue.org.cn/detail.cfm?id=1871406
被背叛的简单性
模拟视频系统表明,即使是一个简单的界面也可能比看起来更复杂且更强大。
- George Phillips
https://queue.org.cn/detail.cfm?id=1755886
Richard L. Sites 博士 于 1959 年编写了他的第一个计算机程序,并将他的大部分职业生涯都投入在硬件和软件之间的边界,尤其对 CPU/软件性能感兴趣。他曾担任数字设备公司 VAX 微代码团队的负责人,然后与 DEC Alpha 处理器的联合架构师 Rich Witek 共事。与 Michael Uhler 一起,他发明了当今几乎所有处理器中都存在的性能计数器。与 Ben Sigelman 等人一起,他帮助设计并大量使用了 Dapper RPC 跟踪工具。与 Ross Biro 一起,他在 Google 构建了 KUTrace 的第一个版本。他曾在 DEC、Adobe 和 Google 从事低开销微代码和软件跟踪工作,最近在特斯拉担任顾问。他曾在麻省理工学院、北卡罗来纳大学和斯坦福大学学习。他拥有 35 项专利,并且是美国国家工程院院士。
版权 © 2018 由所有者/作者持有。出版权已授权给 。
最初发表于 Queue 杂志第 16 卷,第 5 期—
在 数字图书馆 中评论本文