3. 操作系统内核优化:CPU隔离、内存大页、中断绑定与内核参数调优
做高频交易的人都知道,延迟就是金钱。你比别人慢一微秒,可能就错失了一笔交易。我见过太多团队,算法写得再漂亮,底层操作系统没调好,一到行情爆发就掉链子。说白了,操作系统内核优化,就是给你的交易系统铺一条高速公路。
今天咱们聊聊四个核心方向:CPU隔离、内存大页、中断绑定、内核参数调优。这四块搞定了,你的系统才算真正跑在「裸金属」上。
3.1 CPU隔离:把核心「私有化」
先问一个问题:你的交易线程跑在哪个 CPU 上?
默认情况下,Linux 调度器会把线程在各个核心之间迁移。这听起来挺智能,但对高频交易来说,这是灾难。为什么?因为每次迁移都伴随着缓存失效、TLB 刷新,延迟直接飙升。
我个人的习惯是,把物理核心分成两组:一组给操作系统和中断用,另一组专门给交易进程用。怎么做?用 isolcpus 内核启动参数。
# 在 GRUB 配置中添加 isolcpus 参数
# 假设你有 16 个核心(0-15),隔离核心 4-15
GRUB_CMDLINE_LINUX="isolcpus=4-15 nohz_full=4-15 rcu_nocbs=4-15"
# 更新 GRUB 后重启
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
这里我解释一下:isolcpus 告诉调度器别碰这些核心。nohz_full 关闭了这些核心上的定时器中断。rcu_nocbs 把 RCU 回调也挪走了。三个参数一起用,效果最好。
taskset 或 cset 把交易进程绑定到隔离核心上。我一般留一个核心给操作系统,比如核心 0,其他全隔离。
我在项目中遇到过一个问题:隔离了核心,但网络中断还是打到了隔离核心上。后来发现,irqbalance 服务没关。嗯,这里要注意,irqbalance 会自动分配中断,必须禁掉。
systemctl stop irqbalance
systemctl disable irqbalance
3.2 内存大页:减少 TLB 缺失
内存访问延迟,很大一部分来自 TLB(页表缓存)缺失。默认的 4KB 页面,意味着你需要 256 个页表项才能覆盖 1MB 内存。而大页(HugePages)直接把页面大小提升到 2MB 甚至 1GB。
你想想看,TLB 能缓存的条目是有限的。用 4KB 页面,TLB 覆盖范围可能只有几 MB。用 2MB 大页,覆盖范围直接翻 512 倍。这对高频交易这种内存密集型应用来说,效果立竿见影。
配置大页有两种方式:
- 静态大页(HugePages):启动时预留,适合确定性要求高的场景。
- 透明大页(THP):动态分配,但可能引入额外延迟。我个人建议关掉它。
# 预留 1024 个 2MB 大页
echo 1024 > /proc/sys/vm/nr_hugepages
# 或者通过 /etc/sysctl.conf 持久化
vm.nr_hugepages = 1024
# 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
我曾经踩过一个坑:大页预留了,但程序没挂载 hugetlbfs,结果还是用的普通内存。正确的做法是:
# 挂载 hugetlbfs
mount -t hugetlbfs hugetlbfs /dev/hugepages
# 在代码中通过 mmap 使用大页
void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
3.3 中断绑定:让中断「定点」处理
网络数据来了,谁去处理?中断。如果中断随机落在某个核心上,而那个核心正好在跑你的交易线程,那就糟了——上下文切换,延迟飙升。
解决方案很简单:把中断绑定到非隔离核心上。比如,把网卡中断都绑到核心 0 和核心 1 上。
# 查看网卡中断号
cat /proc/interrupts | grep eth0
# 假设中断号是 78,绑定到核心 0
echo 1 > /proc/irq/78/smp_affinity
# 或者用脚本批量绑定
for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | sed 's/://'); do
echo 1 > /proc/irq/$irq/smp_affinity
done
这里 smp_affinity 的值是位掩码。核心 0 对应 1(二进制 0001),核心 1 对应 2(0010),核心 0 和 1 对应 3(0011)。
我记得有一次,客户反馈交易系统偶尔出现毫秒级延迟。查了半天,发现是 USB 中断打到了交易核心上。嗯,USB 中断优先级低,但一旦发生,还是会抢 CPU。所以,我建议把所有不必要的中断都绑到管理核心上。
3.4 内核参数调优:微调每一处细节
内核参数调优,说白了就是告诉 Linux:「别帮我做那些我不需要的优化,我要的是确定性。」
以下是我在高频交易环境中常用的参数:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| kernel.sched_min_granularity_ns | 10000000 | 调度最小粒度,增大可减少上下文切换 |
| kernel.sched_wakeup_granularity_ns | 15000000 | 唤醒抢占粒度,增大可减少抢占 |
| vm.swappiness | 0 | 禁用 swap,避免内存换出导致的延迟 |
| net.core.rmem_max | 134217728 | 增大接收缓冲区,防止丢包 |
| net.core.wmem_max | 134217728 | 增大发送缓冲区 |
| kernel.numa_balancing | 0 | 禁用 NUMA 平衡,避免页面迁移 |
# 写入 /etc/sysctl.conf
kernel.sched_min_granularity_ns = 10000000
kernel.sched_wakeup_granularity_ns = 15000000
vm.swappiness = 0
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
kernel.numa_balancing = 0
# 立即生效
sysctl -p
这里我要特别强调 numa_balancing。NUMA 架构下,访问本地内存和远端内存的延迟差很多。NUMA 平衡会自动迁移页面,但迁移过程会锁住页面,导致访问延迟抖动。在高频交易中,我建议直接关掉。
3.5 知识体系总览
说了这么多,咱们用一张图来总结一下。这张图展示了操作系统内核优化的四个维度,以及它们之间的关系。
这张图里,四个模块是相互独立的,但目标一致:消除抖动,增加确定性。CPU 隔离解决的是调度抖动,内存大页解决的是内存访问抖动,中断绑定解决的是中断抢占抖动,内核参数调优解决的是系统行为的不确定性。
最后说一句:这些优化不是一劳永逸的。每次内核升级、硬件变更,都要重新验证。我见过有人升级内核后忘了重新配置,结果延迟从 5 微秒飙到 50 微秒。嗯,细节决定成败。