CPU占用率是错的(翻译)

原文是性能优化大神Brendan Gregg的一篇17年的博客”CPU Utilization is Wrong”[1],写的很有参考价值,我翻译一下。Youtube上也有与之对应的一个presentation:

我们大家用的“CPU占用率”这个指标存在很大的误导性,而且这种误导近年来越来越严重。什么是CPU占用率,它是指你的处理器到底多忙吗?不,那并不是它所能反映出的。是的,我正要讨论%CPU这个大家都处都在用的性能指标(包括每一种的性能检测软件,包括top工具)。

你以为90%的CPU占用率是这样时,

其实它有可能是这样的:

Stalled指的是处理器不能继续执行指令,这基本是由于CPU在等待内存IO。上边我所画的busy和stalled的比例正是我在真实的生产环境所见的。所以,很有可能你在内存stall时,并没有察觉到。

对我们来说这意味这什么?明白了我们的CPU有多少被stall可以让我们直接将性能优化的努力放到减少不必要的代码和内存IO。能搞清楚CPU占用率中有多少比例的stall成分,任何关注CPU性能的人(尤其当程序运行在是根据CPU来自动扩展的云环境中时)都能获得好处。

到底什么是CPU占用率

CPU占用率这个指标其实就是“非空闲时间”,即CPU没有在跑idle线程。OS内核经常会在上下文切换时进行追踪操作,如果非空闲线程开始跑并在100毫秒后停下来,那么内核认为CPU被全部占用。

这个指标和分时系统一样古老。阿波罗登月舱的指挥计算机(分时系统的先驱)把它的idle线程称为”哑任务(dummy job)”,工程师们通过追踪运行哑任务和其他运行真实任务的cycles来得到这一重要的计算机占用率指标。(就像我以前所写一样。)

那么问题出在哪呢?

当今,CPU比主存更快,因此“等待内存”成为了“CPU占用率”的主要部分。当你在top工具中看到高的%CPU时,你可能会认为处理器(在散热器和风扇下的CPU封装)是瓶颈,不过其实都是那些DRAM内存的问题。

这种情况越来越糟,一直以来,处理器时钟的增速都要大于DRAM访问延迟的增速(“CPU-DRAM鸿沟”,译注:也叫“内存墙”)。这种局面持续到2005年的3 GHz处理器,从那以后,处理器往多核、超线程、多socket发展,这些都提升了对内存子系统的需求。处理器厂商尝试用更智能的CPU cache、更快的内存总线和互联网络来解决这些问题。但是我们仍然经常面临stall问题。

怎么知道CPU正在干什么

通过使用性能监测计数器(Performance Monitoring Counter, PMC) :一种可以用Linux perf或其他工具读取的硬件计数器。比如我们可以这样监测整个系统10秒钟:

一个重要的指标是 instructions per cycle (insns per cycle: IPC),这表示对于每个CPU时钟周期我们平均完成的指令数。IPC这个值越高越好,例子中值为0.78看起来不错,这是表示78%的占用吗?不是的,这个处理器最高可以达到4.0的IPC(这也被称为4-wide处理器,用来说明指令fetch/decode的路径)。这说明,CPU每个时钟周期可以retire(完成)4个指令。所以在一个4-wide处理器上,0.78的IPC说明当前CPU只是它最高运行速度的19.5%。最新的Intel处理器可以达到5-wide。

PMC有上百个,你可以利用它们来深挖性能瓶颈,比如直接测量不同类型的stall周期。

云环境中

如果你在虚拟环境中,你可能访问不到PMC,这取决于你的hypervisor支持不支持。我最近写过The PMCs of EC2: Measuring IPC这篇文章,说明了怎么在AWS EC2 Xen-based的云主机上使用特定PMC的。

一些解释和经验

如果IPC < 1.0,那么很可能是memory stalled,软件调优策略可以考虑减少内存IO,提升CPU cache或内存的局部性,特别是在NUMA系统中。硬件调优可以考虑有大CPU cache、更快内存、总线和互联网络的处理器。

如果IPC > 1.0,那么很可能是“instruction bound”,可以考虑减少代码的执行,即减少不必要的任务和cache操作等。CPU火焰图(CPU flame graphs)是一个很好的分析工具。对于硬件调优,可以尝试更快的时钟频率、更多的核或超线程。

对于我们以上的规则,我用1.0作为IPC分界,为啥这样做?这是基于我以前有关PMC的工作。你也可以找到一个适合你的系统和环境的值:只需要写两个样例负载程序,一个是CPU bound,另一个是memory bound,然后测下它们各自的IPC,取一个中间点。

性能检测工具应该告诉你什么?

每种性能检测工具除了%CPU外,都还应该展示出IPC。或者将%CPU分解为”instruction-retired cycles”和“stalled cycles”,比如可以称为%INS%STL

对应于top工具,Linux有tiptop工具,展示了进程的IPC:

证明CPU占用率具有误导性的其他原因

并非只有memory stall导致了CPU占用率的误导性,还有其他原因,包括:

  • 高温跳闸导致的处理器stall;
  • Turboboost(睿频)导致的时钟频率不稳;
  • 内核根据动态调整导致的时钟频率变化;
  • 求平均值导致的问题:超过一分钟的80%占用,掩盖了100%的爆发占用;
  • Spin locks:CPU是被占用,也有很高的IPC,但程序并无逻辑上的进展。

更新:CPU占用率到底错了没有

现在已经有上百条评论,感谢每个花时间看和感兴趣这个话题的人。总结下我的回复:我一直都没有说iowait(那是磁盘IO),而且如果你知道负载是memory bound,上边也给出了优化建议。

但是CPU占用的确是错的,或者说存在严重的误导性?我认为很多人认为高%CPU意味着处理器单元是瓶颈,正如我之前所说,这是不对的。因为你不能只因CPU占用而做出这个断定,很可能是其他外部原因导致的。这个指标技术上正确吗?如果CPU stall的那些cycles不能挪为它用,他们不就是“被占用的等待”吗(听起来像是个矛盾)?某种意义上来说是这样的,你可以说%CPU是在OS层次上是一个正确的指标,但是它任然有很强的误导性。如果有hyperthread,这些停滞的cycles是可以被用于其他线程的,所以%CPU可能会统计到其实是还可用stalled cycles。所以这个指标就是不对的,这篇文章之我只想解释为题和给出建议,但这个指标技术上确实是有问题的!

你可以说占用率已经不该作为一个指标被使用了,就像Adrian Cockcroft之前所说那样。

结论

CPU占用率已成为一个具有严重误导性的性能指标:它包括了等待内存的cycles,这在当今的负载中很常见。也许%CPU应该重命名为%CYC,即cycles的简写。通过其他一些检测指标(包括IPC),你可以搞明白%CPU真正代表了什么,如果IPC小于1表示内存bound,如果大于1表示instruction bound。我在之前的文章也介绍过IPC和测量他相关的PMC。

性能监测工具除了展示%CPU还应当展示一些PMC指标来进一步说明CPU占用率代表这什么,这就会导致过多的误解。比如可以同时展示%CPUIPC,也可以把%CPU分解成instruction-retired cycles和stalled cycles。有了额外这些监测指标,开发和运维人员都可以更好地优化他们的应用和系统。


[1] CPU Utilization is Wrong, http://www.brendangregg.com/blog/2017-05-09/cpu-utilization-is-wrong.html

[2] What does an idle CPU do? https://manybutfinite.com/post/what-does-an-idle-cpu-do/

发表评论

电子邮件地址不会被公开。 必填项已用*标注