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 时间占比

分析流程

  1. 采样:perf record -F 99 -a -g -- sleep 60
  2. 生成:perf script | stackcollapse-perf.pl | flamegraph.pl
  3. 分析:查找宽平台(热点函数)

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)
  • 应用响应缓慢

分析步骤

  1. 确认 CPU 类型
uptime                    # 查看负载
top                       # 查看 CPU 使用率
mpstat -P ALL 1          # 各核心使用率
  1. 识别热点进程
ps aux --sort=-%cpu | head
pidstat -u 1
  1. 线程级分析
top -H -p <PID>          # 查看线程 CPU
pidstat -t -p <PID> 1    # 线程统计
  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

下一步:学习 内存性能分析与优化 章节。