CPU 利用率错了

原文:CPU Utilization is Wrong · 作者:Brendan Gregg · 发表日期:2017 年 5 月 9 日

本文为原文可见正文的完整中文译文,包含作者后续补充的“CPU 利用率真的错了吗?”一节。保留原文图示、命令与输出。文中的“现在”“较新的处理器”和云平台支持情况均指原文发表时的环境;标为“编辑注”的内容为本次整理补充。

我们都在用的 CPU 利用率指标,其实有很强的误导性,而且这种问题一年比一年严重。CPU 利用率是什么?是处理器有多忙吗?不是,它测量的并不是这个。没错,我说的就是那个人人都在用、随处可见的 %CPU 指标:每一款性能监控产品里都有,top(1) 里也有。

看到 90% 的 CPU 利用率,你可能会以为它表示:

原文示意图:绝大部分时间标为忙碌,右侧较短一段标为空闲等待。
图 1:人们对 90% CPU 利用率的常见理解。Busy 表示“忙碌”,Waiting(idle)表示“等待(空闲)”。原图:Brendan Gregg;这是作者绘制的概念示意图,不是本次实测结果。

但它实际表示的可能是:

原文示意图:非空闲部分进一步分成较短的忙碌时间和较长的停顿等待时间,右侧仍有空闲等待。
图 2:同一段“非空闲时间”中,可能有大量时间在停顿等待。Waiting(stalled)表示“等待(停顿)”。原图:Brendan Gregg;图中比例体现作者的生产经验,不是适用于所有系统的测量结论。

“停顿”(stalled)是指处理器没有在指令执行上取得进展,通常发生在它等待内存 I/O 的时候。上图中忙碌与停顿的比例,是我在生产环境里经常看到的情况。你的处理器很可能大部分时间都在停顿,只是你并不知道。

这对你意味着什么?弄清 CPU 有多少时间处于停顿状态,能够帮助你决定性能优化应当着重减少代码执行,还是减少内存 I/O。任何关注 CPU 性能的人,尤其是在按 CPU 利用率自动扩缩容的云环境中工作的人,都能从了解 %CPU 中的停顿部分获益。

CPU 利用率究竟是什么?

我们称为“CPU 利用率”的指标,实际上是“非空闲时间”:也就是 CPU 没有运行空闲线程的时间。无论使用哪种操作系统,内核通常都会在上下文切换时跟踪这一状态。如果一个非空闲线程开始运行,在 100 毫秒后停止,内核就会把整个 100 毫秒都算作 CPU 被占用的时间。

这个指标的历史与分时系统一样悠久。阿波罗登月舱的制导计算机是早期分时系统的先驱之一,它把空闲线程称为“DUMMY JOB”。工程师会比较运行该任务与运行实际任务所用的周期数,把这当作计算机利用率的重要指标。我以前写过这件事。

那么,这有什么问题?

如今,CPU 的速度已经远远超过主存,而等待内存却占据了所谓“CPU 利用率”的很大一部分。当你在 top(1) 中看到很高的 %CPU 时,可能会以为瓶颈是处理器,也就是散热器和风扇下面的那个 CPU 封装。实际上,瓶颈却可能是旁边的那些 DRAM 内存条。

这一问题一直在加剧。长期以来,处理器厂商提升时钟频率的速度,超过了 DRAM 缩短访问延迟的速度,这就是“CPU 与 DRAM 之间的差距”。到了 2005 年前后、处理器时钟频率达到约 3 GHz 时,这种趋势趋于平缓。此后,处理器通过增加核心、超线程以及多插槽配置来扩展性能,这些变化都对内存子系统提出了更多需求。处理器厂商尝试用更大、更智能的 CPU 缓存,以及更快的内存总线和互连,减轻内存瓶颈。但我们通常仍在停顿等待。

怎样知道 CPU 真正在做什么?

可以使用性能监控计数器(Performance Monitoring Counters,PMCs)。它们是硬件计数器,可以通过 Linux perf 等工具读取。例如,对整个系统测量 10 秒:

# perf stat -a -- sleep 10

 Performance counter stats for 'system wide':

     641398.723351      task-clock (msec)         #   64.116 CPUs utilized            (100.00%)
           379,651      context-switches          #    0.592 K/sec                    (100.00%)
            51,546      cpu-migrations            #    0.080 K/sec                    (100.00%)
        13,423,039      page-faults               #    0.021 M/sec
 1,433,972,173,374      cycles                    #    2.236 GHz                      (75.02%)
   <not supported>      stalled-cycles-frontend
   <not supported>      stalled-cycles-backend
 1,118,336,816,068      instructions              #    0.78  insns per cycle          (75.01%)
   249,644,142,804      branches                  #  389.218 M/sec                    (75.01%)
     7,791,449,769      branch-misses             #    3.12% of all branches          (75.01%)

      10.003794539 seconds time elapsed

这里的关键指标是每周期指令数(instructions per cycle,输出中写作 insns per cycle,简称 IPC),它表示每个 CPU 时钟周期平均完成多少条指令。数值越高越好——这是一个简化的说法。上例中的 0.78 听起来似乎还不错:是不是意味着有 78% 的时间在忙碌?直到你意识到,这款处理器的最高 IPC 是 4.0。

这种设计也被称为 4-wide,这里指的是指令取指、译码通路的宽度。按原文的简化解释,这意味着 CPU 每个时钟周期可以退役,也就是完成四条指令。因此,在一个 4-wide 系统上,IPC 为 0.78,相当于 CPU 只达到了其最高速度的 19.5%。较新的 Intel 处理器可能会发展到 5-wide。

还有数百种 PMC 可供进一步深入分析,包括直接按不同类型测量停顿周期。

在云环境中

如果你使用的是虚拟化环境,可能无法访问 PMC,这取决于 hypervisor 是否向客户机提供支持。我最近发表的《EC2 的 PMC:测量 IPC》介绍了:在基于 Xen 的 AWS EC2 云中,专用宿主机类型现在已经可以使用 PMC。

编辑注:上一段描述的是作者在 2017 年的观察。本次没有验证当前 AWS 实例类型的支持矩阵,也没有在虚拟机上测量。应针对实际实例、虚拟化层和事件支持情况重新核验,不能把历史条件推广为今天所有云实例的能力。

如何解读,以及接下来能做什么

如果 IPC 小于 1.0,你很可能受内存停顿影响。软件优化可以从减少内存 I/O、提高 CPU 缓存利用效果以及改善内存局部性入手,尤其是在 NUMA 系统上。硬件方面,可以考虑使用 CPU 缓存更大的处理器,以及更快的内存、总线和互连。

如果 IPC 大于 1.0,你很可能受指令执行量限制。可以寻找减少代码执行的方法:消除不必要的工作、缓存操作结果等。CPU 火焰图非常适合辅助这类调查。硬件方面,可以尝试更高的时钟频率,以及更多核心或超线程。

在上述经验规则中,我用 IPC 1.0 作为分界。这个数值从哪里来?是我根据过去使用 PMC 的经验自己定的。要得到适合你自己的系统和运行时的值,可以编写两个简单的测试工作负载:一个受 CPU 限制,另一个受内存限制。测量它们的 IPC,再取二者的中间值。

编辑注:1.0 是作者明确承认的经验分界,不是跨处理器、跨运行时通用的诊断标准。应按实际 CPU 架构与工作负载建立基线,并用进一步的硬件计数器证据确认瓶颈。原文没有提供这两个测试工作负载的代码,本次也没有自行执行或编造校准结果。

性能监控产品应该告诉你什么

每一种性能工具都应该在显示 %CPU 的同时显示 IPC。或者,把 %CPU 拆分成有指令退役的周期与停顿周期,例如分别显示为 %INS 和 %STL。

至于 top(1),Linux 上还有 tiptop(1),它能够按进程显示 IPC:

tiptop -                  [root]
Tasks:  96 total,   3 displayed                               screen  0: default

  PID [ %CPU] %SYS    P   Mcycle   Minstr   IPC  %MISS  %BMIS  %BUS COMMAND
 3897   35.3  28.5    4   274.06   178.23  0.65   0.06   0.00   0.0 java
 1319+   5.5   2.6    6    87.32   125.55  1.44   0.34   0.26   0.0 nm-applet
  900    0.9   0.0    6    25.91    55.55  2.14   0.12   0.21   0.0 dbus-daemo

编辑注:上面是原文中的界面文本,不是本次运行截图或当前版本输出;保留了原文截断的进程名称 dbus-daemo。%INS 和 %STL 是作者提出的展示方式,不能直接假定为所有处理器都具有、且可通用相加的事件名称。

CPU 利用率具有误导性的其他原因

造成误导的并不只有内存停顿周期,还包括:

  • 温度保护触发,导致处理器停顿。
  • Turbo Boost 改变时钟频率。
  • 内核通过 SpeedStep 改变时钟频率。
  • 平均值掩盖波动:一分钟内平均利用率为 80%,可能隐藏了短时达到 100% 的峰值。
  • 自旋锁:CPU 被占用,IPC 也很高,但应用在逻辑上并没有取得进展。

更新:CPU 利用率真的错了吗?

这篇文章在本站下方以及其他地方收到了数百条评论,包括 Hacker News 和 Reddit。感谢大家花时间参与,并对这个话题感兴趣。概括一下我的回复:我讨论的根本不是 iowait,那涉及磁盘 I/O;而且,只要知道自己受内存限制,就有具体可采取的优化措施,前文已经列出。

不过,CPU 利用率是真的“错了”,还是仅仅有很强的误导性?我认为,很多人会把较高的 %CPU 理解为处理单元本身就是瓶颈。正如前面所说,这种理解是错的。在那个时候,你还不知道真正的瓶颈在哪里,而它往往位于处理单元之外。

这个指标在技术上正确吗?如果 CPU 的停顿周期不能被其他工作利用,那么是否可以说它们是在“被占用着等待”——尽管这听起来有些自相矛盾?在某些情况下,的确可以说,%CPU 作为操作系统层面的指标在技术上正确,却非常容易误导人。不过,有了超线程以后,另一个线程可以利用这些停顿周期。这样一来,%CPU 就可能把实际上仍可利用的周期统计成已经占用。这就是错的。

我在本文中想重点讨论的是解读问题和可行的解决方案。但没错,这个指标在技术层面也存在问题。

你也可以说,“利用率”这个指标早就有问题了,正如 Adrian Cockcroft 此前讨论过的那样。

结语

CPU 利用率已经变成一个很容易误导人的指标:它把等待主存的周期也算了进去,而这部分周期可能在现代工作负载中占据主导。也许应该把 %CPU 改名为 %CYC,即 cycles(周期)的缩写。借助每周期指令数 IPC 等额外指标,你才能弄清 %CPU 真正意味着什么。按照本文给出的经验规则,IPC 小于 1.0 很可能意味着受内存限制,IPC 大于 1.0 则很可能意味着受指令执行量限制。

我在上一篇文章中介绍过 IPC,也介绍了测量它所需的性能监控计数器 PMC。

所有显示 %CPU 的性能监控产品——也就是所有性能监控产品——都应该同时显示 PMC 指标,解释这些利用率数值究竟代表什么,避免误导最终用户。例如,可以把 %CPU 与 IPC 放在一起显示,也可以显示有指令退役的周期与停顿周期。有了这些指标,开发者和运维人员才能更好地决定如何优化应用与系统。

译文核验日期:2026 年 10 月 5 日。正文与两张配图版权归 Brendan Gregg;原文站点导航、广告、评论加载器、评论内容及 HTML 注释中的未展示草稿不属于本文可见正文,未纳入译文。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容