CPU 参数调优:从源头掐断延迟

系统延迟高,很多时候不是代码逻辑有问题,而是 CPU 本身没跑对。说白了,CPU 的调度策略、频率切换、甚至它落在哪个核上,都直接影响你的处理速度。这章我带你实操一下 CPU 层面的调优。记得有一次,我们一个实时交易系统,平均延迟总是下不去,后来我发现——CPU 在空转的时候被降频了,等突发流量一来,它得先花几毫秒把频率提上来,这几毫秒,就是致命的。

CPU 参数调优,主要关注三个方向:频率、亲和性、调度策略。下面我们一个一个说。

CPU 参数调优核心框架 CPU 延迟调优 频率调优 亲核性调优 调度策略调优 调节器、动态调频 taskset、NUMA 绑核 FIFO、RR、CFS 切换

1. 频率调优:别让 CPU 偷懒

CPU 为了省电,默认会动态调整频率。这个叫动态调频(DVFS)。但省电和省延迟,往往是冲突的。你想想看,一个本来就忙的核,突然被降频,请求就得排队等更久。

我的做法是:对于延迟敏感的服务,直接锁定性能模式。你可以通过 cpupower 工具操作。

实用命令:
cpupower frequency-set -g performance —— 把所有核设为性能模式
cpupower frequency-info —— 查看当前频率和可用调节器

调节器(Governor)有好几种,我列个表,你一看就明白:

调节器行为适用场景
performance始终跑在最高频率低延迟、高吞吐
powersave始终跑在最低频率省电、后台任务
ondemand负载高时升频,空闲时降频通用场景(延迟一般)
conservative缓慢升降频省电场景,不适合突发
schedutil由调度器决策频率较新内核,兼顾延迟与能效
核心结论: 搞低延迟,就用 performance。我在项目中遇到过一次,用 ondemand 时,每 100ms 的定时任务有时会漂到 300ms。换成 performance 后,漂移直接消失。说白了,省那点电,不值得牺牲延迟的稳定性。

2. 亲核性调优:把关键进程绑死在指定核上

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

  1. 减少缓存缺失: 同一个核的 L1/L2 缓存是热的。进程跑来跑去,缓存就凉了,每次都得从内存读。
  2. 避免中断干扰: 把中断交给别的核处理,关键业务核专心处理逻辑。
  3. 隔离噪音: 其他任务再乱,也影响不到被绑定的核。

操作方式有两种:

  • taskset: 启动时绑核。taskset -c 0,1 ./your_app,表示只让进程跑在核 0 和核 1 上。
  • cpuset: 更精细的隔离。可以创建单独的 cpuset 组,把某些 CPU 和内存节点独分配给进程。
我建议的实操步骤:
1. 查看 NUMA 拓扑:lscpu | grep -i numa
2. 确认物理核与逻辑核对应关系:lscpu -e
3. 把业务进程绑在一个物理核对应的逻辑核对上(不要跨 NUMA 节点绑核,那会导致跨片访问,增加延迟)
避坑指南
我曾经把两个高负载服务绑在同一个物理核的两个超线程上,结果发现延迟反而升高了。因为超线程是共享执行单元的,两个服务互相争抢,还不如各占一个物理核。所以记住:低延迟场景,优先使用不同的物理核,而不是超线程核。

3. 内核调度策略:让实时任务不再排队

Linux 默认的 CFS(完全公平调度器)追求的是公平,而不是响应速度。想想看,你的关键线程和几十个普通线程同等优先级竞争 CPU,延迟能低才怪。

解决方案:使用实时调度策略。主要有两种:

  • SCHED_FIFO: 先进先出,优先级高的线程只要就绪就立即抢占 CPU,直到主动让出或阻塞。
  • SCHED_RR: 轮转调度,相同优先级的线程分时间片轮流执行。

如果你的业务是“处理一个请求,然后等待下一个”,用 FIFO 更合适。如果任务量均匀,用 RR 也不错。

设置方法很简单,常见的有两种:

  1. 启动时设置: 用 chrt 工具。chrt -f 99 ./your_app 表示将进程设为 FIFO 策略,优先级 99(最高)。
  2. 运行时设置: 通过 sched_setscheduler 系统调用在代码内实现。
注意: 实时优先级范围是 1-99。99 最高,0 是普通 CFS。我一般给特别关键的业务线程设 99,但不建议所有线程都抢这个优先级,否则系统管理任务(如 kswapd)可能无法运行,导致死锁。

下面是一个用 chrt 设置调度策略的例子:

# 查看当前进程调度策略
ps -eo pid,cls,pri,comm | grep your_app

# 设置 FIFO 优先级 90
chrt -f -p 90 $(pgrep your_app)

# 验证设置
chrt -p $(pgrep your_app)
我曾经踩过的坑:
把某个 CPU 密集型的计算线程设为 FIFO 99,结果它占满 CPU,导致网卡中断处理延迟超高,网络直接卡死。所以实时调度必须配合 CPU 隔离和中断亲核性一起用。把中断绑到其他核,计算线程绑到专用核,两不耽误。

4. 综合调优流程:把这些串起来

别一个个试,我建议你按照这个顺序来:

  1. 隔离核心: 在 GRUB 中用 isolcpus=2,3 把核 2、3 从系统调度中移出来,专供业务使用。
  2. 设置频率: 所有被隔离的核和中断核都设为 performance 模式,避免频率波动。
  3. 中断亲核性: 把网卡、存储等中断 IRQ 绑到非业务核上(比如核 0、1)。
  4. 进程绑核: 业务进程通过 taskset 绑到核 2、3。
  5. 调度策略: 对业务线程执行 chrt -f 99。

调完后,用 perf stat -e context-switches, migrations 看上下文切换和 CPU 迁移次数,这两个数字应该接近为 0,才算调到位。

5. 验证效果:眼见为实

调优前和调优后,用 cyclictest 测一下延迟分布:

# 安装
apt-get install -y rt-tests

# 在核 2 上运行测试,绑定到核 2
cyclictest -l 100000 -m -S -p 99 -a 2

重点关注输出中的 Max latency。我在一个项目上,调优后最大延迟从 500μs 降到了 20μs。效果立竿见影。

嗯,到这里 CPU 层面的调优就差不多了。记住一个原则:让 CPU 少操心、少切换、少变化。频率锁定、核绑死、策略用实时,这三板斧下去,延迟基本就稳了。