CPU 性能调优:从架构到实战

CPU 调优,说白了就是让处理器干得更快、更稳。我做了这么多年性能优化,发现很多人一上来就调参数、改内核,结果越调越慢。为什么?因为没搞懂 CPU 到底怎么工作的。

这一章,咱们从最基础的 CPU 架构讲起,一步步深入到调度器。你跟着我的思路走,保证能建立起完整的调优知识体系。

核心观点:CPU 调优不是玄学,是架构、调度、亲和性三者的平衡。先理解硬件,再动软件。

CPU 性能调优知识体系 CPU 性能调优 CPU 架构基础 密集型任务优化 上下文切换与中断 亲和性与调度器 流水线 & 缓存 CPU 绑定 & 隔离 中断亲和性 CFS / 实时调度 调优路径:先理解架构 → 识别任务类型 → 减少切换 → 绑定核心 → 选择调度器 每个环节都有对应的工具和指标,后面会逐一展开

CPU 架构基础:别把 CPU 当黑盒

很多人觉得 CPU 就是个执行指令的盒子。其实不然。现代 CPU 内部极其复杂,有流水线、分支预测、多级缓存、乱序执行……这些硬件特性直接影响你的程序性能。

我记得有一次帮客户优化一个数据库查询,发现同样的 SQL 在不同型号的 CPU 上性能差了三倍。查到最后,问题出在 L3 缓存大小上。你想想看,如果不懂架构,这种问题根本无从下手。

架构组件 对性能的影响 调优关注点
L1/L2 缓存 命中延迟 ~1-10 纳秒 数据局部性、缓存行对齐
L3 缓存 命中延迟 ~30-50 纳秒 多核共享、NUMA 亲和性
分支预测器 预测失败惩罚 ~15-20 周期 减少分支、使用 likely/unlikely
乱序执行窗口 窗口大小 ~100-200 指令 减少指令依赖、提高 ILP

我的习惯:拿到一台新机器,第一件事就是看 lscpu 和 cpuid 的输出。搞清楚缓存大小、NUMA 节点、超线程状态,再开始调优。

CPU 密集型任务优化:让核心跑满

CPU 密集型任务,说白了就是计算为主、IO 很少的场景。比如视频编码、科学计算、密码学运算。这类任务的目标很简单:让 CPU 核心一直干活,别闲着。

但怎么做到?我见过太多人写多线程程序,结果线程数比核心数多好几倍,上下文切换开销反而拖慢了整体性能。

// 错误示范:线程数过多
for (int i = 0; i < 100; i++) {
    pthread_create(&threads[i], NULL, worker, NULL);
}

// 正确做法:线程数 = CPU 核心数
int core_count = sysconf(_SC_NPROCESSORS_ONLN);
for (int i = 0; i < core_count; i++) {
    pthread_create(&threads[i], NULL, worker, NULL);
}

我曾经踩过的坑:在一个 32 核的机器上开了 64 个线程做矩阵运算,结果性能还不如 32 线程。用 perf 一看,上下文切换每秒超过 10 万次。后来绑定了核心,性能直接翻倍。

上下文切换与中断:看不见的开销

上下文切换,就是 CPU 从一个进程切到另一个进程。每次切换都要保存寄存器、刷新 TLB、加载新进程的状态。一次切换大概消耗 1-10 微秒。听起来不多?如果每秒切换几万次呢?

中断更麻烦。硬件中断来了,CPU 必须停下当前工作去处理。如果中断处理写得不好,或者中断亲和性没配好,某个核心可能被中断淹没。

我建议你用 vmstat 1 看看 cs(context switch)列。如果这个值超过 5 万,就要警惕了。

指标 健康范围 需要关注
上下文切换/秒 < 10,000 > 50,000
中断/秒 < 5,000 > 20,000
CPU 软中断时间 < 1% > 5%

排查命令:cat /proc/interrupts 可以看每个 CPU 的中断分布。如果某个核心的中断数特别高,用 echo mask > /proc/irq/IRQ_NUMBER/smp_affinity 重新分配。

CPU 亲和性与绑核:让线程扎根

CPU 亲和性,就是让某个进程或线程固定在某个核心上跑。好处很明显:缓存命中率高、减少迁移开销。

我个人习惯,对于延迟敏感的服务,一律绑核。比如 Redis、Nginx、DPDK 应用。不绑核的话,操作系统调度器可能会把进程到处迁移,缓存全废了。

// 使用 sched_setaffinity 绑核
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);  // 绑定到 CPU 0
sched_setaffinity(0, sizeof(cpuset), &cpuset);

// 命令行工具 taskset
taskset -c 0-3 ./myapp   // 绑定到 0-3 号核心

注意:绑核不是万能的。如果绑定的核心还要处理中断,或者和其他进程共享,效果会打折扣。我一般会预留一个核心专门处理中断,其他核心跑业务。

Linux CPU 调度器:选对策略

Linux 默认的调度器是 CFS(完全公平调度器)。它对大多数场景都够用,但如果你做实时任务或者批处理,就得换策略。

调度策略主要有这么几种:

  • SCHED_OTHER(CFS):默认策略,公平分配时间片。适合普通应用。
  • SCHED_FIFO:实时策略,先入先出。优先级高的任务不被打断。
  • SCHED_RR:实时策略,时间片轮转。同优先级任务轮流执行。
  • SCHED_BATCH:适合 CPU 密集型批处理任务,减少切换。
  • SCHED_IDLE:只在 CPU 空闲时运行,适合后台低优先级任务。
// 设置实时调度策略
struct sched_param param;
param.sched_priority = 99;
sched_setscheduler(0, SCHED_FIFO, &param);

我的建议:非实时场景别用 SCHED_FIFO 或 SCHED_RR。一旦优先级设错,可能导致系统卡死。我曾经在测试环境把 SSH 守护进程设成了 SCHED_FIFO,结果优先级太低,系统直接失联……

实战:一个完整的调优案例

最后,我分享一个真实案例。一个视频转码服务,跑在 48 核的机器上。刚开始性能很差,每秒只能转 5 帧。

排查步骤:

  1. 看 CPU 使用率:发现只有 30%,说明 CPU 没跑满。
  2. 看上下文切换:每秒 8 万次,太高了。
  3. 看中断分布:所有中断都集中在 CPU 0。
  4. 调整:把中断分散到 4 个核心,业务进程绑到其他 44 个核心,线程数设为 44。
  5. 结果:上下文切换降到 5000 次/秒,CPU 使用率升到 95%,转码速度提升到 45 帧。

你看,调优不是靠猜,是靠数据。每一步都有工具支撑,每一步都有理论依据。

记住:CPU 调优的核心是减少浪费。浪费来自上下文切换、缓存未命中、中断干扰。找到浪费,消除浪费,性能自然就上去了。