CPU 性能分析与优化
CPU 是系统的核心计算资源,理解 CPU 性能瓶颈对于系统优化至关重要。
CPU 性能基础
CPU 性能指标
时钟频率(Clock Speed):
- CPU 每秒执行的时钟周期数
- 单位:GHz(十亿次/秒)
- 更高频率 ≠ 更好性能(需考虑 IPC)
IPC(Instructions Per Cycle):
- 每个时钟周期执行的指令数
- 反映 CPU 架构效率
- 现代 CPU 通过流水线、超标量提高 IPC
核心和线程:
- 物理核心:独立的处理单元
- 逻辑核心:超线程(HT)技术虚拟的核心
- 超线程性能提升约 20-30%
缓存层次:
CPU 核心
├─ L1 缓存(数据/指令):~64KB,1-3 周期
├─ L2 缓存:~256KB-512KB,10-20 周期
└─ L3 缓存(共享):~8MB-64MB,40-80 周期
↓
主内存:GB 级别,200-300 周期
CPU 使用率的本质
什么是 CPU 使用率:
- 采样周期内 CPU 非空闲时间的百分比
- 不是 CPU 的"繁忙程度"
- 100% 使用率可能仍在等待(I/O、内存)
CPU 时间分类:
- User Time(usr):执行用户空间代码
- System Time(sys):执行内核代码(系统调用)
- I/O Wait(iowait):等待 I/O 完成
- IRQ/SoftIRQ:硬件中断和软件中断处理
- Idle:空闲时间
- Steal:虚拟化环境中被宿主机窃取的时间
理解 iowait:
- iowait 高不一定是 I/O 瓶颈
- 只表示 CPU 在等待 I/O 时空闲
- 需结合 I/O 指标综合判断
CPU 性能分析方法
负载平均值(Load Average)
定义:
- 处于可运行(R)或不可中断睡眠(D)状态的进程平均数
- 输出三个值:1 分钟、5 分钟、15 分钟
解读负载:
假设 4 核 CPU:
- 负载 < 4.0:系统轻松
- 负载 = 4.0:CPU 刚好饱和
- 负载 > 4.0:有进程等待
趋势分析:
1min 5min 15min
4.5 3.2 2.1 → 负载上升(需关注)
2.1 3.2 4.5 → 负载下降(恢复正常)
负载 vs CPU 使用率:
- 负载高、CPU 低:I/O 密集
- 负载高、CPU 高:CPU 密集
- 负载低、CPU 高:短暂尖峰
上下文切换
什么是上下文切换:
- 保存当前进程状态
- 加载新进程状态
- 包括寄存器、页表、内核栈
切换类型:
- 自愿切换:进程主动放弃 CPU(I/O、睡眠)
- 非自愿切换:时间片用完、被抢占
开销分析:
- 直接开销:保存/恢复状态(~微秒级)
- 间接开销:缓存失效、TLB 刷新(更大)
过多切换的原因:
- 进程/线程数过多
- 锁竞争严重
- 时间片过小
- 中断频繁
CPU 缓存
缓存命中率:
- L1 命中:最快,~4 周期
- L2 命中:较快,~10 周期
- L3 命中:慢,~40 周期
- 内存访问:很慢,~200 周期
缓存行(Cache Line):
- 缓存的最小单位,通常 64 字节
- 一次加载整个缓存行
- 空间局部性优化的基础
伪共享(False Sharing):
线程 A 访问变量 a
线程 B 访问变量 b
如果 a 和 b 在同一缓存行:
├─ 线程 A 修改 a → 缓存行失效
├─ 线程 B 的缓存行也失效
└─ 性能严重下降
解决方案:
├─ 填充(Padding)分离变量
└─ 对齐到不同缓存行
CPU 瓶颈识别
CPU 密集型特征
判断标准:
- CPU 使用率持续 > 80%
- 系统时间(sys)占比低(< 20%)
- iowait 占比低(< 10%)
- 运行队列长度 > CPU 核心数
典型场景:
- 密集计算(科学计算、图像处理)
- 加密解密
- 压缩解压缩
- 复杂算法
系统调用开销
过高系统时间的原因:
- 频繁的小 I/O 操作
- 大量系统调用
- 内核锁竞争
- 上下文切换
优化方向:
- 减少系统调用次数(批处理)
- 使用用户态库(避免内核态切换)
- 异步 I/O
- 零拷贝技术
中断开销
中断类型:
- 硬中断(IRQ):硬件触发
- 软中断(SoftIRQ):内核延迟处理
高中断开销症状:
- softirq/IRQ 进程 CPU 使用率高
- si/hi 列数值大(top 输出)
- 网络或磁盘性能下降
常见原因:
- 网卡中断过多(高流量)
- 磁盘 I/O 频繁
- 中断亲和性配置不当
CPU 性能优化
进程优先级
nice 值调整:
nice 值范围:-20(最高优先级)到 19(最低优先级)
默认值:0
影响:
nice = 0 → 基准 CPU 时间
nice = 10 → 约 1/3 的 CPU 时间
nice = -10 → 约 3 倍的 CPU 时间
使用场景:
├─ 降低批处理任务优先级
├─ 提高关键服务优先级
└─ 防止后台任务影响前台
实时优先级:
- SCHED_FIFO:先进先出,无时间片
- SCHED_RR:轮转,有时间片
- 优先级:0-99(数字越大越高)
- 谨慎使用(可能导致系统无响应)
CPU 亲和性
概念:
- 将进程/线程绑定到特定 CPU
- 提高缓存命中率
- 减少迁移开销
使用场景:
- 性能敏感应用
- 实时系统
- NUMA 优化
- 隔离干扰
软亲和性 vs 硬亲和性:
- 软亲和性:优先但非强制
- 硬亲和性:强制绑定
注意事项:
- 可能导致负载不均
- 减少调度灵活性
- 需要理解拓扑结构
多核并行化
并行化策略:
1. 数据并行
└─ 将数据分片,多线程并行处理
2. 任务并行
└─ 不同任务并行执行
3. 流水线并行
└─ 生产者-消费者模式
Amdahl 定律限制:
- 串行部分限制加速比
- 锁竞争降低并行效率
- 线程间通信开销
并行度选择:
- 不是线程越多越好
- 考虑 CPU 核心数
- 减少锁竞争
- 避免过度切换
算法优化
时间复杂度优化:
O(n²) → O(n log n) → O(n) → O(log n) → O(1)
示例:
暴力搜索 O(n²)
↓
排序 + 二分 O(n log n)
↓
哈希表 O(n)
分支预测优化:
- CPU 分支预测失败导致流水线清空
- 减少分支(查表法)
- 使分支可预测(排序后处理)
SIMD 指令:
- 单指令多数据
- 一次操作处理多个数据
- AVX、SSE 等指令集
- 编译器自动向量化
减少系统调用
批处理 I/O:
差:循环中调用 write()
for (int i = 0; i < 1000; i++) {
write(fd, &data[i], 1); // 1000 次系统调用
}
好:一次写入
write(fd, data, 1000); // 1 次系统调用
用户态缓冲:
- stdio 库(fread/fwrite)
- 减少内核态切换
- 批量刷新
vDSO 优化:
- 虚拟动态共享对象
- 某些系统调用在用户态执行
- gettimeofday、clock_gettime 等
CPU 性能分析工具
基础工具
top/htop:
关键指标:
%CPU:进程 CPU 使用率
TIME+:累计 CPU 时间
S:进程状态(R=运行, S=睡眠, D=不可中断)
快捷键:
1:显示每个 CPU
H:显示线程
c:显示完整命令
mpstat:
mpstat -P ALL 1
关键指标:
%usr:用户态 CPU
%sys:内核态 CPU
%iowait:等待 I/O
%irq:硬中断
%soft:软中断
%idle:空闲
pidstat:
pidstat -u 1 # CPU 使用
pidstat -w 1 # 上下文切换
pidstat -t 1 # 线程级统计
关键指标:
%CPU:CPU 使用率
cswch/s:自愿上下文切换
nvcswch/s:非自愿上下文切换
高级分析工具
perf:
性能事件统计:
perf stat ./program
CPU 采样:
perf record -g ./program
perf report
实时监控:
perf top
关键事件:
cycles:CPU 周期
instructions:执行的指令数
cache-misses:缓存未命中
branch-misses:分支预测失败
火焰图:
- 可视化 CPU 时间分布
- X 轴:函数按字母排序(非时间)
- Y 轴:调用栈深度
- 宽度:CPU 时间占比
分析流程:
- 采样:perf record -F 99 -a -g -- sleep 60
- 生成:perf script | stackcollapse-perf.pl | flamegraph.pl
- 分析:查找宽平台(热点函数)
CPU 缓存分析
perf 缓存事件:
perf stat -e L1-dcache-loads,L1-dcache-load-misses \
-e LLC-loads,LLC-load-misses \
./program
计算命中率:
L1 命中率 = (loads - misses) / loads
cachegrind:
valgrind --tool=cachegrind ./program
输出:
I1 miss rate:L1 指令缓存未命中率
D1 miss rate:L1 数据缓存未命中率
LL miss rate:最后一级缓存未命中率
CPU 性能调优实例
案例1:CPU 高负载分析
症状:
- 系统负载持续 > 10(4 核 CPU)
- 应用响应缓慢
分析步骤:
- 确认 CPU 类型
uptime # 查看负载
top # 查看 CPU 使用率
mpstat -P ALL 1 # 各核心使用率
- 识别热点进程
ps aux --sort=-%cpu | head
pidstat -u 1
- 线程级分析
top -H -p <PID> # 查看线程 CPU
pidstat -t -p <PID> 1 # 线程统计
- 函数级分析
perf record -g -p <PID> sleep 30
perf report
常见原因和解决:
- 无限循环:代码 bug,修复逻辑
- 低效算法:优化算法复杂度
- 频繁系统调用:批处理、用户态缓冲
- 锁竞争:减小临界区、无锁编程
案例2:上下文切换过多
症状:
- vmstat 显示 cs 列很大(> 10000)
- 系统 CPU 占比高
分析:
vmstat 1
pidstat -w 1
perf record -e sched:sched_switch -g
优化方向:
- 减少线程数
- 增大时间片
- 使用协程/绿色线程
- 优化锁粒度
案例3:缓存未命中优化
症状:
- IPC 低(< 1.0)
- perf 显示高缓存未命中
优化策略:
- 数据结构重排:热数据放一起
- 访问模式优化:顺序访问代替随机
- 减少数据大小:压缩、去除冗余
- 对齐到缓存行:避免伪共享
NUMA 架构优化
NUMA 概述
UMA vs NUMA:
UMA(统一内存访问):
CPU1 ─┐ ┌─ CPU2
├─ 内存 ─┤
CPU3 ─┘ └─ CPU4
所有 CPU 访问内存延迟相同
NUMA(非统一内存访问):
┌─ CPU1/2 ─ 本地内存
│
├─ CPU3/4 ─ 本地内存
│
└─ 互连网络
访问本地内存快,远程内存慢
NUMA 性能影响
本地 vs 远程访问:
- 本地访问:~100ns
- 远程访问:~200ns(2 倍延迟)
- 跨 NUMA 带宽受限
NUMA 感知重要性:
- 内存分配策略
- 进程/线程调度
- 缓存一致性开销
NUMA 优化
查看 NUMA 拓扑:
numactl --hardware
lscpu | grep NUMA
内存分配策略:
- 本地分配:在访问 CPU 的本地节点分配
- 交叉分配:在所有节点均匀分配
- 绑定分配:指定节点分配
进程绑定:
numactl --cpunodebind=0 --membind=0 ./program
下一步:学习 内存性能分析与优化 章节。