第三章:操作系统内核优化——把Linux调教成交易机器

各位同学,今天我们来聊聊操作系统内核优化。说实话,很多做量化交易的朋友,代码写得漂亮,策略回测也漂亮,但一上实盘就掉链子。为什么?因为操作系统没伺候好。你想想看,Linux内核默认是为通用场景设计的,它要照顾数据库、Web服务器、桌面应用……唯独没考虑过你那个争分夺秒的套利程序。

我个人习惯,拿到一台新机器,第一件事不是装软件,而是先调内核参数。这就像赛车手拿到车,先调悬挂和轮胎,而不是先踩油门。下面我把这些年积累的调优经验,拆成三个核心部分来讲。

3.1 CPU隔离与中断亲和性

先说CPU隔离。这个概念其实很简单——把某些CPU核心从系统的通用调度中剥离出来,专门给你的交易进程用。为什么要这么做?因为Linux默认会把进程和中断到处乱扔,你的交易线程可能刚在CPU0跑得好好的,下一秒就被踢到CPU7去了,缓存全凉了。

核心思路: 用isolcpus内核启动参数隔离核心,再用taskset绑定进程。

我在项目中遇到过一件事:有一次回测系统在盘后跑得好好的,但一到开盘就频繁出现微秒级的抖动。查了半天,发现是网卡中断和交易进程抢CPU。后来把中断绑到CPU0-1,交易进程锁在CPU2-3,问题立刻消失。

具体怎么做?看这里:

# 在GRUB_CMDLINE_LINUX中添加
isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3

# 然后更新grub
grub2-mkconfig -o /boot/grub2/grub.cfg

嗯,这里要注意:nohz_fullrcu_nocbs是配套的,它们能让隔离的核心彻底摆脱内核时钟中断和RCU回调的干扰。说白了,就是让这些核心只跑你的用户态代码,连内核都别来烦它。

中断亲和性怎么调?我建议用set_irq_affinity脚本,或者手动写:

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

# 将中断绑定到CPU0-1(掩码0x03)
echo 03 > /proc/irq/123/smp_affinity

小技巧: 可以用lscpu -e查看CPU拓扑,把中断绑在同一个NUMA节点上的核心,避免跨节点访问。

3.2 内存大页(HugePages)

接下来是内存大页。为什么需要它?标准内存页是4KB,对于交易系统这种动辄占用几十GB内存的应用,页表会变得巨大。TLB(页表缓存)就那么点地方,根本装不下。结果就是每次内存访问都要查页表,延迟飙升。

大页把页大小变成2MB甚至1GB。你想想看,原来需要512个4KB页才能覆盖2MB,现在一个页就搞定了。TLB的命中率从90%能直接干到99.9%以上。

配置方法有两种:

  • 静态大页(HugePages):启动时预留,不能动态调整
  • 透明大页(THP):动态分配,但有碎片化风险

我个人强烈建议用静态大页。透明大页虽然方便,但它在分配时可能会触发内存 compaction,造成不可预测的延迟。我曾经在一个高频交易客户的机器上,发现每隔几分钟就会出现一次毫秒级的延迟尖峰,最后定位到就是THP在后台做内存整理。关掉THP,换静态大页,世界清净了。

# 预留1024个2MB大页
echo 1024 > /proc/sys/vm/nr_hugepages

# 或者通过grub启动参数
default_hugepagesz=2M hugepagesz=2M hugepages=1024

然后你的程序在分配内存时,需要用mmap加上MAP_HUGETLB标志,或者用libhugetlbfs库。Java的话,JVM参数加个-XX:+UseLargePages就行。

警告: 大页一旦预留,其他进程就不能用了。所以别贪心,留够你的交易进程用的量就行。我一般留比实际需求多10%的余量。

3.3 NUMA绑定

最后是NUMA绑定。现在的服务器都是多路CPU,每个CPU有自己的内存控制器。访问本地内存和远程内存的延迟差多少?大概30%-50%。如果你的交易进程在CPU0上跑,却分配到了CPU1的内存,每次内存访问都多花几十纳秒。积少成多,你的套利窗口可能就关上了。

NUMA绑定的核心原则就一句话:让进程在本地CPU核心上运行,并使用本地内存

怎么查NUMA拓扑?

numactl --hardware

输出会告诉你每个节点有哪些核心和内存。然后你可以用numactl启动进程:

# 绑定到节点0的核心和内存
numactl --cpunodebind=0 --membind=0 ./trade_engine

或者更精细一点,用taskset绑核心,再用mbind绑内存。我习惯在代码里直接调用sched_setaffinitymbind,这样更可控。

避坑指南: 我曾经犯过一个错误——只绑了CPU没绑内存。结果进程虽然在CPU0上跑,但内存分配器还是从节点1分配了内存。后来用numastat -p <pid>一看,远程内存访问占比高达40%。所以记住:CPU和内存要一起绑。

知识体系总览

下面这张图把今天讲的内容串起来了。你可以看到,从硬件中断到内存访问,每个环节都有对应的优化手段。

Linux内核优化知识体系 CPU隔离 内存大页 NUMA绑定 isolcpus 隔离核心 中断亲和性绑定 nohz_full + rcu_nocbs 静态大页 (HugePages) 透明大页 (THP) 慎用 mmap MAP_HUGETLB numactl 绑定节点 CPU+内存联合绑定 numastat 监控验证 目标:确定性延迟 + 可预测性能

最后总结一下。内核优化这件事,说白了就是跟操作系统抢控制权。默认的Linux是个好好先生,什么任务都接,什么中断都处理。但你的交易程序需要的是「专宠」——专属的CPU、专属的内存、专属的中断处理路径。做到这三点,你的延迟抖动至少能降一个数量级。

嗯,今天就先到这里。记住,调完参数一定要用perf stat或者cyclictest验证效果。我曾经见过有人调完参数自我感觉良好,结果一测延迟反而更高了——原来是中断绑错了核心。所以,信数据,别信感觉。

专注资料整理