CPU性能分析:CPU使用率分析、上下文切换、CPU绑定与亲和性

说到CPU性能,很多人第一反应就是看使用率。嗯,这没错,但远远不够。我在项目中踩过不少坑,今天就把这些经验掰开揉碎了讲给你听。

一、CPU使用率分析——别被数字骗了

CPU使用率,说白了就是CPU忙活的时间占比。但这里有个坑——你以为100%就是满负荷?不一定。

核心要点:CPU使用率分为用户态(user)、系统态(sys)、等待I/O(iowait)、软中断(softirq)等。光看总使用率,你根本不知道瓶颈在哪。

我习惯用top命令先扫一眼,然后立刻按1看每个核心的情况。你想想看,如果8个核里7个空闲,1个跑满,那总使用率才12.5%,但你的服务可能已经卡得不行了。

举个例子,我曾经接手过一个API服务,CPU总使用率才30%,但响应时间动不动就超时。查了半天,发现是某个worker进程绑死在了某个核上,其他核都在看热闹。这就是典型的「伪空闲」现象。

实战命令速查

命令用途我的习惯
top -H查看线程级CPU消耗先看进程,再看线程
mpstat -P ALL 1每个核的实时使用率每秒刷新,持续观察
pidstat -u 1进程级CPU统计定位具体哪个进程在吃CPU

我的小技巧:%iowait超过10%时,别急着优化代码,先看看磁盘是不是扛不住了。我曾经有一次优化了三天代码,结果发现是磁盘队列太深,白忙活一场。

二、上下文切换——看不见的性能杀手

上下文切换,就是CPU从一个任务切到另一个任务。每次切换都要保存寄存器、刷新TLB,这些都是纯开销。

为什么会成为杀手?因为切换太频繁了。我见过一个极端案例,某服务每秒上下文切换达到20万次,CPU光忙着切换了,真正干活的时间不到一半。

怎么查上下文切换?

# 查看系统级上下文切换
vmstat 1 5

# 查看进程级上下文切换
pidstat -w 1

# 查看具体原因(自愿/非自愿切换)
pidstat -w -I 1

输出里有个关键字段:cswch/s(自愿切换)和nvcswch/s(非自愿切换)。

  • 自愿切换:线程主动让出CPU,比如等I/O、等锁。数量多说明I/O密集或锁竞争激烈。
  • 非自愿切换:时间片到了被强制抢走。数量多说明线程太多,CPU不够分。

避坑指南:我曾经遇到过一个诡异的性能问题——服务偶尔卡顿几秒钟。查了三天,发现是某个定时任务启动了200个线程去扫数据库,导致其他线程被频繁抢占。非自愿切换从几百飙升到几万。解决方案很简单:限制线程池大小,或者把定时任务绑到特定核上。

我个人建议,上下文切换超过1万次/秒就要警惕了。超过5万次/秒,基本可以断定是性能瓶颈。

三、CPU绑定与亲和性——把线程焊死在核上

CPU亲和性,就是让某个进程或线程只跑在指定的CPU核心上。为什么要这么做?

你想想看,如果进程频繁在不同核之间迁移,缓存就白费了。L1/L2缓存里的数据全得重来,性能损失很大。尤其是对延迟敏感的服务,比如高频交易、实时音视频,CPU亲和性几乎是必选项。

怎么设置亲和性?

# 使用taskset绑定进程到指定核
taskset -c 0,1 python app.py   # 绑定到核0和核1

# 运行时修改
taskset -p -c 2 12345          # 将PID 12345绑定到核2

# 使用sched_setaffinity(Python代码)
import os
import psutil

p = psutil.Process()
p.cpu_affinity([0, 1])  # 只允许在核0和核1上运行

重要提醒:绑定CPU不是万能的。如果绑定的核上还有其他高负载进程,反而会互相干扰。我习惯先看/proc/interrupts,确认哪些核的中断少,再把服务绑上去。

实战场景:Nginx + uWSGI的亲和性配置

我记得有一次优化一个Python Web服务,8核机器,uWSGI起了8个worker。默认情况下,worker会在所有核上乱跳。我做了两件事:

  1. 把Nginx的worker进程绑定到核0-1
  2. 把uWSGI的8个worker分别绑定到核2-7

效果很明显:CPU缓存命中率提升了约15%,P99延迟从120ms降到了85ms。说白了,就是让每个worker「定居」下来,别到处搬家。

NUMA架构下的亲和性

现在的服务器大多是NUMA架构。跨NUMA节点访问内存,延迟比本地访问高不少。我建议:

  • numactl --hardware查看NUMA拓扑
  • 尽量让进程和它使用的内存在同一个NUMA节点
  • numactl --cpunodebind=0 --membind=0 python app.py绑定

我的经验:在NUMA机器上,如果发现内存访问延迟异常高,先检查是不是跨节点了。用perf stat -e numa_hit,numa_miss一看便知。我曾经有个服务,numa_miss占比高达40%,绑到同一节点后直接降到了5%以下。

四、综合实战:一次完整的CPU性能分析

假设你的Python服务响应变慢了,按我的排查流程走一遍:

  1. 先看整体top看CPU使用率,vmstat 1看上下文切换
  2. 再看细节mpstat -P ALL 1看每个核的负载是否均衡
  3. 定位进程pidstat -u -w 1找到最吃CPU的进程
  4. 深入线程top -H -p PID看哪个线程在忙
  5. 检查亲和性taskset -p PID看当前绑定情况
  6. 分析中断cat /proc/interrupts | grep CPU看中断分布

这套流程我用了好几年,基本能覆盖90%的CPU性能问题。记住,别一上来就怀疑代码写得不好,先看看是不是CPU调度出了问题。

最后提醒:CPU亲和性设置要谨慎。绑得太死,万一那个核挂了,你的服务也跟着挂了。我一般会留1-2个核给系统用,不绑定任何业务进程。这样即使业务把CPU吃满,系统还能正常响应。

好了,CPU这块就聊到这儿。下一章我们讲内存,那又是一个大坑。到时候见。