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会在所有核上乱跳。我做了两件事:
- 把Nginx的worker进程绑定到核0-1
- 把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服务响应变慢了,按我的排查流程走一遍:
- 先看整体:
top看CPU使用率,vmstat 1看上下文切换 - 再看细节:
mpstat -P ALL 1看每个核的负载是否均衡 - 定位进程:
pidstat -u -w 1找到最吃CPU的进程 - 深入线程:
top -H -p PID看哪个线程在忙 - 检查亲和性:
taskset -p PID看当前绑定情况 - 分析中断:
cat /proc/interrupts | grep CPU看中断分布
这套流程我用了好几年,基本能覆盖90%的CPU性能问题。记住,别一上来就怀疑代码写得不好,先看看是不是CPU调度出了问题。
最后提醒:CPU亲和性设置要谨慎。绑得太死,万一那个核挂了,你的服务也跟着挂了。我一般会留1-2个核给系统用,不绑定任何业务进程。这样即使业务把CPU吃满,系统还能正常响应。
好了,CPU这块就聊到这儿。下一章我们讲内存,那又是一个大坑。到时候见。