第4章:操作系统内核调优:CPU隔离、中断绑定与内存大页,榨干Linux最后一滴性能
说实话,很多做量化交易的朋友,花了大价钱买服务器,堆了最好的网卡和CPU,结果延迟还是下不去。问题出在哪?十有八九是操作系统没调好。
Linux内核默认是为通用场景设计的——它要照顾数据库、Web服务、桌面应用,甚至你后台跑的定时任务。但咱们做高频交易,要的是极致的确定性。说白了,就是让CPU只干一件事,别被乱七八糟的中断和调度打断。
这一章,我就带你把这几个关键点调明白。CPU隔离、中断绑定、内存大页,这三板斧下去,延迟能降一个数量级。
核心目标:让交易进程独占CPU核心,避免上下文切换和缓存污染,同时减少TLB miss。
4.1 CPU隔离:把交易进程关进“单间”
Linux默认的进程调度器,会把你的交易进程在各个核心之间来回迁移。每次迁移,CPU缓存就废了——L1、L2里的数据全得重新加载。这在微秒级别的交易里,简直是灾难。
我个人的习惯是,用 isolcpus 内核参数把几个核心隔离出来。这些核心不会被普通进程使用,只有你指定的进程才能跑在上面。
4.1.1 配置isolcpus
在GRUB启动参数里加上:
isolcpus=2,3,4,5 nohz_full=2,3,4,5 rcu_nocbs=2,3,4,5
解释一下:
- isolcpus:把核心2-5从调度器中移除,普通进程不会跑上去
- nohz_full:关闭这些核心的周期性时钟中断,减少不必要的打断
- rcu_nocbs:把RCU回调从这些核心上卸载,避免后台任务干扰
我的经验:隔离的核心数量不要太多。我一般留2-4个核心给交易进程,剩下的给系统和其他服务。你想想看,如果所有核心都隔离了,系统连网卡中断都没地方处理了。
4.1.2 使用cpuset做精细控制
如果不想重启机器,可以用cpuset动态分配:
# 创建cpuset
mkdir /sys/fs/cgroup/cpuset/trading
echo 2-5 > /sys/fs/cgroup/cpuset/trading/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/trading/cpuset.mems
# 把交易进程放进去
echo $TRADING_PID > /sys/fs/cgroup/cpuset/trading/cgroup.procs
嗯,这里要注意:cpuset的配置在重启后会失效。所以生产环境我建议直接改GRUB参数,一劳永逸。
4.2 中断绑定:别让网卡中断打扰交易核心
网卡收到行情数据时,会产生中断。默认情况下,中断可能落在任何一个核心上。如果恰好落在你的交易核心上,那延迟就炸了——交易进程被中断处理程序抢占了CPU。
我曾经遇到过一个问题:某次回测时延迟突然飙升到200微秒,查了半天发现是网卡中断和交易进程抢同一个核心。从那以后,我养成了绑定中断的习惯。
4.2.1 查看当前中断分布
cat /proc/interrupts | grep eth0
你会看到类似这样的输出:
CPU0 CPU1 CPU2 CPU3
98: 1234 56 78 90 IR-PCI-MSI eth0-TxRx-0
99: 567 89 123 45 IR-PCI-MSI eth0-TxRx-1
4.2.2 绑定中断到指定核心
把网卡中断绑定到非交易核心上:
# 将eth0的中断绑定到CPU0和CPU1
echo 3 > /proc/irq/98/smp_affinity
echo 3 > /proc/irq/99/smp_affinity
这里的 3 是位掩码,二进制是 0011,表示CPU0和CPU1。如果绑定到CPU2,就是 4(二进制 0100)。
避坑指南:我曾经犯过一个错——把中断全部绑定到一个核心上。结果那个核心被中断打爆了,反而导致中断处理延迟增加。正确的做法是:把中断分散到2-3个非交易核心上,每个核心处理一部分队列。
4.3 内存大页:减少TLB Miss,提升内存访问速度
交易系统里,内存访问是最大的瓶颈之一。尤其是行情数据量大的时候,频繁的TLB miss会让内存访问延迟翻倍。
Linux默认的内存页大小是4KB。对于动辄几十GB的共享内存来说,页表项多得吓人。TLB(快表)根本装不下,每次访问内存都要查好几级页表。
说白了,大页就是把页大小从4KB变成2MB甚至1GB。页表项少了,TLB命中率自然就上去了。
4.3.1 配置HugePages
# 预留1024个2MB的大页
echo 1024 > /proc/sys/vm/nr_hugepages
# 查看是否成功
grep HugePages_Total /proc/meminfo
在代码里使用大页:
#include <sys/mman.h>
void* addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
4.3.2 透明大页(THP)的坑
Linux还支持透明大页(Transparent Huge Pages),它会自动把连续的内存页合并成大页。听起来很美,但实际用起来问题很多。
我个人的建议是:关掉THP,手动管理大页。为什么?
- THP的合并和拆分过程会触发内存 compaction,导致延迟抖动
- THP对共享内存的支持不好,容易造成内存浪费
- 手动管理大页,你能精确控制哪些内存用大页,哪些不用
# 关闭透明大页
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
我的经验:对于行情共享内存,我一般用2MB的大页。对于订单簿这种频繁更新的数据结构,用4KB小页反而更好——因为大页的TLB miss惩罚虽然小,但大页的分配和释放开销更大。你想想看,频繁分配释放2MB的大页,那延迟反而上去了。
4.4 知识体系总览
下面这张图,是我自己总结的延迟优化知识体系。你可以对照着看,这一章讲的是最底层的内核调优部分。
4.5 实战检查清单
调优完成后,我建议你按这个清单检查一遍:
| 检查项 | 命令 | 期望结果 |
|---|---|---|
| CPU隔离生效 | taskset -cp $$ |
交易进程只跑在隔离核心上 |
| 中断绑定正确 | cat /proc/interrupts |
网卡中断不在交易核心上 |
| 大页已启用 | grep HugePages /proc/meminfo |
HugePages_Total > 0 |
| THP已关闭 | cat /sys/kernel/mm/transparent_hugepage/enabled |
输出 always [never] |
| nohz_full生效 | cat /sys/devices/system/cpu/isolated |
显示隔离的核心列表 |
重要提醒:调优完成后,一定要做压力测试。我曾经在测试环境调得完美,上线后却发现隔离的核心被某个内核线程偷偷占用了。用 perf top 盯一段时间,确认没有异常中断和调度。
好了,这一章的内容就到这里。内核调优是延迟优化的基石,但也是最容易被忽视的环节。你想想看,如果操作系统都在跟你抢资源,再好的算法也跑不出低延迟。
把这些配置加到你的部署脚本里,让每一台交易服务器都跑在最佳状态。