CPU 参数调优:从源头掐断延迟
系统延迟高,很多时候不是代码逻辑有问题,而是 CPU 本身没跑对。说白了,CPU 的调度策略、频率切换、甚至它落在哪个核上,都直接影响你的处理速度。这章我带你实操一下 CPU 层面的调优。记得有一次,我们一个实时交易系统,平均延迟总是下不去,后来我发现——CPU 在空转的时候被降频了,等突发流量一来,它得先花几毫秒把频率提上来,这几毫秒,就是致命的。
CPU 参数调优,主要关注三个方向:频率、亲和性、调度策略。下面我们一个一个说。
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 亲和性,就是让指定进程只能在某个核上跑。为什么要这么做?
- 减少缓存缺失: 同一个核的 L1/L2 缓存是热的。进程跑来跑去,缓存就凉了,每次都得从内存读。
- 避免中断干扰: 把中断交给别的核处理,关键业务核专心处理逻辑。
- 隔离噪音: 其他任务再乱,也影响不到被绑定的核。
操作方式有两种:
- taskset: 启动时绑核。
taskset -c 0,1 ./your_app,表示只让进程跑在核 0 和核 1 上。 - cpuset: 更精细的隔离。可以创建单独的 cpuset 组,把某些 CPU 和内存节点独分配给进程。
1. 查看 NUMA 拓扑:
lscpu | grep -i numa2. 确认物理核与逻辑核对应关系:
lscpu -e3. 把业务进程绑在一个物理核对应的逻辑核对上(不要跨 NUMA 节点绑核,那会导致跨片访问,增加延迟)
我曾经把两个高负载服务绑在同一个物理核的两个超线程上,结果发现延迟反而升高了。因为超线程是共享执行单元的,两个服务互相争抢,还不如各占一个物理核。所以记住:低延迟场景,优先使用不同的物理核,而不是超线程核。
3. 内核调度策略:让实时任务不再排队
Linux 默认的 CFS(完全公平调度器)追求的是公平,而不是响应速度。想想看,你的关键线程和几十个普通线程同等优先级竞争 CPU,延迟能低才怪。
解决方案:使用实时调度策略。主要有两种:
- SCHED_FIFO: 先进先出,优先级高的线程只要就绪就立即抢占 CPU,直到主动让出或阻塞。
- SCHED_RR: 轮转调度,相同优先级的线程分时间片轮流执行。
如果你的业务是“处理一个请求,然后等待下一个”,用 FIFO 更合适。如果任务量均匀,用 RR 也不错。
设置方法很简单,常见的有两种:
- 启动时设置: 用
chrt工具。chrt -f 99 ./your_app表示将进程设为 FIFO 策略,优先级 99(最高)。 - 运行时设置: 通过
sched_setscheduler系统调用在代码内实现。
下面是一个用 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. 综合调优流程:把这些串起来
别一个个试,我建议你按照这个顺序来:
- 隔离核心: 在 GRUB 中用
isolcpus=2,3把核 2、3 从系统调度中移出来,专供业务使用。 - 设置频率: 所有被隔离的核和中断核都设为
performance模式,避免频率波动。 - 中断亲核性: 把网卡、存储等中断 IRQ 绑到非业务核上(比如核 0、1)。
- 进程绑核: 业务进程通过 taskset 绑到核 2、3。
- 调度策略: 对业务线程执行
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 少操心、少切换、少变化。频率锁定、核绑死、策略用实时,这三板斧下去,延迟基本就稳了。