CPU 利用率背后的真相,你知道吗?
1、引言
当CPU利用率90%时,你以为情形是这样滴:
image
而实际却可能是这样滴:
image
这说明CPU并不是90%的时间都在有效的工作,其中有很大一部分时间是在等待(Stalled),这个Stalled类型等待与Idle类型等待是不一样的。Idle类型的等待也称为空闲等待,是CPU真实的空闲时间。而Stalled类型等待却常常是由于CPU指令流水中断导致,造成这种中断的原因一般是资源竞争或者数据依赖。多数情况下是在等待程序的访存操作,而这其中又以读操作为主。
在Stalled等待期间,无法继续执行指令,这意味着当前程序无法继续进行。图中 “Stalled” 状态所占的比例是依据生产环境中的典型场景计算而来,具有普遍的现实意义。
然而,大多数CPU 处于Stalled类型等待状态时,你无法知晓,这是因为 CPU 利用率这个指标没有告诉你这一点。然而这一点对于指导代码优化,提高运行效率至关重要,值得分析研究。
2、CPU 利用率的真实含义是什么?
通常意义的CPU利用率指的是 “non-idle time”:即CPU不执行 idle thread 的时间在统计时段中的占比。
假设一个 non-idle thread 开始运行,100ms 后结束,内核会认为在这段时间内 CPU 利用率为 100%,这种度量方式起源于分时复用系统。早在阿波罗登月舱的导航计算机中,idle thread 就已经存在,当时被叫做 “DUMMY JOB”。工程师通过比对运行 “DUMMY JOB” 和 “实际任务” 的时间比例来衡量导航系统的利用率。
那么这个所谓的“利用率”到底有什么问题呢?
当今时代,CPU 执行速度远远大于内存访问速度,等待访存的时间成为占用 CPU 时间的主要部分。当你在 top 中看到很高的 “%CPU”,你可能认为处理器是瓶颈,而实际的瓶颈却可能是内存。
在过去很长的一段时间内,CPU频率的增长速度都要大于DRAM延迟的降低速度,这也就是常说的内存墙问题(CPU DRAM gap)。2005年前后,处理器厂商开始采用多核和超线程技术,在加上多处理器架构的使用,导致访存需求的急剧上升,进一步加剧了内存墙问题。尽管Cache策略的采用在一定程度上缓解了这种瓶颈,但仍然有很多程序深受CPU Stall的困扰。
3、 如何真正辨别 CPU 在做些什么?
在 PMC(Performance Monitoring Counters) 的帮助下,我们能看到更多的 CPU 运行状态信息。下图是perf 采集了10秒内全部 CPU 的运行状态。
# 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
这里重点关注的核心度量指标是 IPC(instructions per cycle),它表示平均每个 CPU cycle 执行的指令数量,很显然该数值越大表示性能越好。上图中IPC 为 0.78,看起来还不错,但是不是意味着CPU有78%的时间处于忙碌状态呢?现代处理器一般有多条流水线,运行 perf 的那台机器,IPC 的理论值可达到 4.0。
如果从 IPC的角度来看,这台机器只运行到其处理器最高速度的 19.5%(0.78 / 4.0)。幸运的是,在处理器内部,有很多 PMU event,可用来帮助分析造成 CPU stall的原因。PMU的使用需要熟悉处理器微架构,可以参考 Intel SDM。
4、最佳实践是什么?
如果 IPC < 1.0, 此时CPU的利用率高,很可能是由于Memory stall占主导的原因导致,此时可从软件和硬件两个方面来解决这个问题。软件方面:减少不必要的访存操作,提升 cache 命中率,尽量访问本地节点内存;硬件方面:增加 cache 容量,加快访存速度,提升总线带宽。
如果IPC > 1.0, 很可能是计算密集型的程序。可以试图减少执行指令的数量:消除不必要的工作。火焰图CPU flame graphs,非常适用于分析这类问题。硬件方面:尝试超频,或使用更多的 core 或 hyperthread。作者根据PMU相关的工作经验,设定了1.0这个阈值,用于区分访存密集型(memory-bound)和计算密集型(cpu-bound)程序。读者可以根据自己的实际工作平台,合理调整这个阈值。
5、性能工具应该告诉我们什么?
作者认为,性能工具中使用 %CPU 时都应该附带上 IPC,或者将 %CPU 拆分为指令执行消耗 cycle(%INS) 和 stalled 的 cycle(%STL)。对应到 top,在 Linux 系统有一个能够显示每个处理器 IPC 的工具 tiptop:
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
6、其他可能让 CPU 利用率引起误解的因素
除了访存导致的 stall 容易让人误解 CPU 利用率外,还有其他一些因素:
(1)温度原因导致处理器 stall;
(2)Turboboost 干扰时钟速率;
(3)内核使得时钟速率加快;
(4)统计平均带来的问题:1分钟利用率平均 80%,掩盖了中间 100% 部分;
(5)自旋锁: CPU 一直在被使用,同时 IPC 也很高,但是应用逻辑上并没有任何进展。
http://www.brendangregg.com/blog/2017-05-09/cpu-utilization-is-wrong.html