4. 操作系统调优:内核旁路、中断亲和性与CPU隔离

做高频交易系统,说白了就是在跟操作系统抢时间。

你写的代码再快,如果内核给你来个上下文切换,或者中断把CPU抢走了,那延迟直接崩盘。我个人习惯,拿到一台新机器,第一件事就是把这些“隐形杀手”按死。

4.1 内核旁路:绕过那个“中间商”

为什么需要内核旁路?

标准网络栈,数据从网卡到应用,要经过内核。内核要复制数据、要调度、要切换上下文。这一套下来,几十微秒就没了。在高频交易里,几十微秒够对手抢你十次了。

内核旁路的思路很简单:让应用直接跟网卡对话

核心手段:

  • DPDK (Data Plane Development Kit):Intel 推出的用户态网络库。应用通过 UIO 或 VFIO 直接操作网卡寄存器,绕过内核协议栈。
  • Solarflare / Mellanox 的 OpenOnload:硬件厂商的方案,通过内核旁路 + TCP 卸载,延迟能压到 1-2 微秒。
  • RDMA (Remote Direct Memory Access):内存直接读写,连 CPU 都绕过了。适合极低延迟场景。

我记得有一次帮客户调优,他们用的标准内核栈,延迟一直在 20 微秒左右徘徊。换成 DPDK 后,直接降到 2 微秒。客户当场就愣住了。说白了,内核就是那个“中间商”,你绕开它,速度自然就上去了。

我的建议:

如果你们用的是标准 Linux 发行版,先别急着上 DPDK。先试试 setsockopt() 里的 SO_BUSY_POLLSO_PRIORITY。有时候软件层面的优化就够了,别一上来就动硬件。

4.2 中断亲和性:别让中断到处乱跑

中断来了,CPU 得停下手里的活去处理。如果中断每次都落在不同的 CPU 上,那缓存就全废了。

中断亲和性,就是把特定设备的中断绑定到固定的 CPU 核心上。这样,那个核心的 L1/L2 缓存里,永远存着中断处理的热数据。

怎么配?

# 查看网卡中断号
cat /proc/interrupts | grep eth0

# 设置中断亲和性(假设中断号 78,绑定到 CPU 0)
echo 1 > /proc/irq/78/smp_affinity

# 或者用 irqbalance 工具(但建议关掉它,手动绑定更可控)
systemctl stop irqbalance
systemctl disable irqbalance

我曾经踩过一个坑:某次上线前,发现延迟抖动特别大。查了半天,发现是 irqbalance 在后台自动迁移中断。关掉它,手动绑定后,抖动直接消失。嗯,这里要注意:生产环境一定要关掉 irqbalance

配置项 推荐值 说明
irqbalance 关闭 手动绑定更可控
smp_affinity 绑定到专用核心 避免中断干扰业务
RPS/RFS 关闭 软件分发增加延迟

4.3 CPU 隔离:给关键任务留个“单间”

操作系统默认会公平调度所有进程。但公平,对高频交易来说就是灾难。你想想看,你的做市线程正在处理行情,突然被调度出去,让给一个日志进程。那几毫秒的切换,可能就错过了一笔交易。

CPU 隔离,就是把某些核心从内核的调度器中“摘”出来。这些核心只运行你指定的任务,其他进程和线程都不能碰。

Linux 内核提供了 isolcpus 启动参数:

# 在 GRUB 配置中添加
isolcpus=2,3

# 然后使用 taskset 绑定你的做市线程到这些核心
taskset -c 2,3 ./market_maker

更狠一点,可以用 cpuset 子系统:

# 创建 cpuset
mkdir /sys/fs/cgroup/cpuset/hft
echo 2-3 > /sys/fs/cgroup/cpuset/hft/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/hft/cpuset.mems

# 把进程 PID 加入
echo $PID > /sys/fs/cgroup/cpuset/hft/cgroup.procs

警告:

隔离 CPU 后,那个核心上的内核线程(比如 ksoftirqd)也会被隔离。如果中断处理不及时,可能导致网络丢包。我建议至少留一个核心给内核,别全隔离了。

4.4 知识体系图:延迟优化的“三板斧”

下面这张图,是我自己总结的延迟优化路径。从硬件到软件,层层递进。

高频做市系统延迟优化全流程 硬件层 网卡:Solarflare / Mellanox CPU:高主频、大缓存 内存:DDR5 / HBM PCIe:Gen4/Gen5 操作系统层 内核旁路:DPDK / OpenOnload 中断亲和性:绑定到专用核心 CPU隔离:isolcpus / cpuset 关闭 irqbalance / 关闭超线程 应用层 无锁数据结构 内存池 / 预分配 忙等待 vs 事件驱动 分支预测优化

这张图里,操作系统层是承上启下的关键。硬件再好,系统没调好,延迟照样下不去。反过来,系统调好了,哪怕硬件差一点,也能跑出不错的成绩。

4.5 避坑指南:我踩过的那些坑

  • 超线程(Hyper-Threading):我建议关掉。超线程共享 L1/L2 缓存,一个核心上的两个逻辑线程会互相干扰。延迟敏感场景,关掉它。
  • NUMA 亲和性:内存访问有远近之分。你的做市线程和它用的网卡、内存,最好在同一个 NUMA 节点上。用 numactl 绑定。
  • 内核版本:别用太新的内核。有些“优化”反而引入延迟。我个人习惯用长期支持版(LTS),比如 5.10 或 5.15。
  • 电源管理:把 CPU 频率调到最高,关闭节能模式。用 cpupower 设置 performance 策略。

一个小技巧:

调优完成后,用 perf stat 看看上下文切换次数。如果每秒超过 1000 次,说明还有优化空间。我一般压到 100 次以下才算合格。

操作系统调优,说白了就是“减少干扰”。把中断、调度、缓存污染这些干扰降到最低,你的系统才能跑出极限性能。嗯,这一章的内容就到这里。下一章我们聊聊应用层的优化——无锁编程和内存管理。


专注资料整理