企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
# 引言 可能你认为的 90% CPU 利用率意味着这样的情形: ![](https://ws2.sinaimg.cn/large/006tNc79ly1g1sp3jhxpfj30u002i0sl.jpg) 而实际却可能是这样的 ![](https://ws4.sinaimg.cn/large/006tNc79ly1g1sp57jf53j30u002lt8m.jpg) CPU 并非 90% 的时间都在忙着,很大一部分时间在等待,或者说“**停顿(Stalled)**”了。这种情况表示处理器流水线停顿,一般由**资源竞争、数据依赖**等原因造成。多数情况下表现为等待访存操作,其中又以**读操作为主**。在停顿周期内,**不能执行指令**,这意味着你的程序不往前走。值得注意的是,图中 “Stalled” 状态所占的比例是作者依据生产环境中的典型场景计算而来,具有普遍现实意义。因此,大多时候 CPU 处于停顿状态,而你却不知道,因为 CPU 利用率这个指标没有告诉你真相。通过进一步分析 CPU 停顿的原因,可以指导代码优化,提高执行效率,这是我们深入理解CPU微架构的动力之一。 # CPU 利用率的真实含义是什么? 我们通常所说的CPU利用率是指 “**non-idle time**”:即CPU不执行 idle thread 的时间。操作系统内核会在上下文切换时记录CPU的运行时间。假设一个 non-idle thread 开始运行,100ms 后结束,内核会认为这段时间内 CPU 利用率为 100%。这种度量方式源于分时复用系统。早在阿波罗登月舱的导航计算机中,idle thread 当时被叫做 “DUMMY JOB”,工程师通过比对运行 “DUMMY JOB” 和 “实际任务” 的时间来衡量导航系统的利用率。 那么这个所谓“利用率”的问题在哪儿呢? 当今时代,CPU 执行速度远远大于内存访问速度,等待访存的时间成为占用 CPU 时间的主要部分。当你在 top 中看到很高的 “%CPU”,你可能认为处理器是瓶颈,但实际上却是内存。在过去很长一段时间内,CPU 频率增长的速度大于 DRAM 访存延时降低的速度(CPU DRAM gap),直到2005年前后,处理器厂商们才开始放弃“频率路线”,转向多核、超线程技术,再加上多处理器架构,这些都导致访存需求急剧上升。尽管厂商通过增大 cache 容量、优化 cache 策略、提升总线带宽来试图缓解访存瓶颈,但我们的程序仍深受 CPU stall 困扰。 # 如何真正辨别 CPU 在做些什么? 在 PMC(Performance Monitoring Counters) 的帮助下,我们能看到更多的 CPU 运行状态信息。下图中,`perf` 采集了10秒内全部 CPU 的运行状态。 ```bash # perf stat -a -- sleep 10 Performance counter stats for 'system wide': 320075.987964 cpu-clock (msec) # 31.997 CPUs utilized 75,544 context-switches # 0.236 K/sec 4,044 cpu-migrations # 0.013 K/sec 668 page-faults # 0.002 K/sec 6,330,615,683 cycles # 0.020 GHz 2,201,747,556 instructions # 0.35 insn per cycle 447,782,180 branches # 1.399 M/sec 26,550,335 branch-misses # 5.93% of all branches 10.003433562 seconds time elapsed ``` 这里我们重点关注的核心度量指标是 **IPC(instructions per cycle)**,它表示`平均每个 CPU cycle 执行的指令数量`,很显然该数值越大性能越好。上图中 IPC 为 0.78,看起来还不错,是不是 78% busy 呢?现代处理器一般有多条流水线,运行 `perf` 的那台机器,IPC 的理论值可达到 4.0。如果我们从 IPC  的角度来看,这台机器只运行到其处理器最高速度的 19.5%(0.78 / 4.0)。幸运的是,在处理器内部,有很多 PMU event,可用来帮助我们分析造成 CPU stall 的原因。用好 PMU 需要我们熟悉处理器微架构,可以参考 Intel SDM。 # 最佳实践是什么? 如果 IPC < 1.0, 很可能是 Memory stall 占主导,可从软件和硬件两个方面考虑这个问题。软件方面:减少不必要的访存操作,提升 cache 命中率,尽量访问本地节点内存;硬件方面:增加 cache 容量,加快访存速度,提升总线带宽。 如果IPC > 1.0, 很可能是计算密集型的程序。可以试图减少执行指令的数量:消除不必要的工作。火焰图CPU flame graphs,非常适用于分析这类问题。硬件方面:尝试超频、使用更多的 core 或 hyperthread。作者根据PMU相关的工作经验,设定了1.0这个阈值,用于区分访存密集型(memory-bound)和计算密集型(cpu-bound)程序。读者可以根据自己的实际工作平台,合理调整这个阈值。 # 性能工具应该告诉我们什么? 作者认为,性能工具中使用 %CPU 时都应该附带上 IPC,或者将 %CPU 拆分为指令执行消耗 cycle(%INS) 和 stalled 的 cycle(%STL)。对应到 `top`,在 Linux 系统有一个能够显示每个处理器 IPC 的工具 `tiptop`: