第三章:操作系统内核调优——把Linux调教成量化交易的最佳拍档

做市系统跑在Linux上,就像赛车手坐在驾驶舱里。操作系统就是那个方向盘和油门,调不好,再牛的算法也白搭。今天咱们聊聊内核调优,说白了就是让Linux为低延迟交易服务。

我个人习惯把内核调优分成四大块:网络、内存、调度,还有中断和CPU隔离。每一块都有坑,也有捷径。咱们一个一个来。

3.1 网络参数调优:别让网卡成为瓶颈

做市系统对网络延迟极其敏感。你想想看,一个订单从发出到成交,中间经过网卡、协议栈、内核缓冲区……任何一个环节卡壳,延迟就上去了。

我遇到过最典型的问题:某次实盘时,系统突然出现毫秒级的延迟抖动。查了半天,发现是net.core.rmem_default设得太小,导致UDP包在接收队列里排队。嗯,从那以后我养成了一个习惯——先调网络缓冲区。

3.1.1 网络缓冲区调大

核心参数就这几个:

# 接收缓冲区默认值和最大值(字节)
net.core.rmem_default = 262144
net.core.rmem_max = 16777216

# 发送缓冲区默认值和最大值
net.core.wmem_default = 262144
net.core.wmem_max = 16777216

# TCP读写缓冲区(最小值、默认值、最大值)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

这里有个技巧:rmem_maxwmem_max建议设到16MB以上。为什么?因为做市系统经常要处理突发流量,缓冲区小了,丢包就是灾难。

我的经验: 别把rmem_default设得太大,否则每个连接都占16MB内存,连接数一多反而浪费。我一般设256KB作为默认值,让系统按需增长。

3.1.2 减少TCP延迟

做市系统里,TCP连接通常用于管理指令或行情订阅。延迟敏感的场景,我建议关闭Nagle算法:

# 关闭Nagle算法
net.ipv4.tcp_nodelay = 1

# 减少TIME_WAIT连接数
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1

注意:tcp_tw_reuse只适用于客户端,服务端别乱开。我曾经在服务端开了这个参数,结果连接复用导致数据错乱,排查了一整天。

3.1.3 网卡队列与RPS/RFS

现代网卡支持多队列,每个队列可以绑定到不同CPU核心。但有些老网卡不支持,这时候可以用RPS(Receive Packet Steering)来模拟:

# 开启RPS,将网卡中断分散到多个CPU
echo "f" > /sys/class/net/eth0/queues/rx-0/rps_cpus

我个人建议:如果网卡支持RSS(Receive Side Scaling),优先用硬件队列。RPS是软件方案,有额外开销。

3.2 内存参数调优:大页内存是神器

做市系统里,内存访问延迟直接影响性能。尤其是行情处理和订单簿重建,频繁的内存分配和释放会带来不可预测的延迟。

大页内存(HugePages)就是解决这个问题的。它把默认的4KB页换成2MB甚至1GB的页,减少了TLB miss,说白了就是让CPU少跑几趟内存。

3.2.1 配置HugePages

先看系统支持多大页:

grep Hugepagesize /proc/meminfo
# 输出:Hugepagesize: 2048 kB

然后分配大页数量。假设系统有64GB内存,我一般分32GB给大页:

# 分配16384个2MB大页(共32GB)
echo 16384 > /proc/sys/vm/nr_hugepages

永久生效需要改/etc/sysctl.conf

vm.nr_hugepages = 16384
注意: 分配大页后,这部分内存就被锁定了,普通进程用不了。我见过有人把全部内存都分给大页,结果系统OOM了。建议留20%给内核和系统进程。

3.2.2 透明大页(THP)关掉

Linux有个透明大页功能,自动把普通页合并成大页。听起来不错,但实际坑很多。它会在运行时触发内存规整(compaction),导致延迟抖动。

我建议直接关掉:

echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

永久生效:

# /etc/default/grub 中添加
transparent_hugepage=never

3.2.3 内存锁定与mlockall

做市进程的内存页如果被换出到swap,延迟会飙升。我习惯在代码里调用mlockall()锁定所有内存页:

# 在C/C++代码中
#include <sys/mman.h>
mlockall(MCL_CURRENT | MCL_FUTURE);

同时关闭swap:

# 关闭swap
swapoff -a
# 永久关闭:注释掉/etc/fstab中的swap行

3.3 调度与CPU隔离:让核心只干一件事

做市系统的核心逻辑,比如订单簿更新、策略计算,需要独占CPU核心。如果被其他进程打断,延迟就不可控了。

CPU隔离(isolcpus)就是干这个的。它告诉内核:这些核心别跑普通进程,只跑我指定的任务。

3.3.1 配置isolcpus

在GRUB启动参数里添加:

isolcpus=2,3,4,5

这样核心2-5就被隔离了。系统进程和中断不会跑到这些核心上。

然后通过tasksetcset把做市进程绑定到隔离核心:

# 绑定进程到核心2-5
taskset -c 2-5 ./market_maker
我的习惯: 隔离核心的数量要留有余地。比如16核的机器,我隔离8个核心给做市系统,剩下8个给系统、监控、日志等。别把鸡蛋都放一个篮子里。

3.3.2 中断亲和性设置

网卡中断如果落在隔离核心上,会打断做市进程。所以要把中断绑定到非隔离核心。

查看中断号:

cat /proc/interrupts | grep eth0

假设中断号是78,绑定到核心0:

echo "1" > /proc/irq/78/smp_affinity

这里1是位掩码,表示核心0。核心1是2,核心2是4,以此类推。

我建议写个脚本,在系统启动时自动设置:

#!/bin/bash
# 将所有网卡中断绑定到核心0-1
for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | sed 's/://'); do
    echo "3" > /proc/irq/$irq/smp_affinity
done

3.3.3 调度策略:SCHED_FIFO

做市进程的调度策略,我推荐用SCHED_FIFO(实时优先级)。这样内核会优先调度它,不会被普通进程抢占。

# 设置实时优先级(范围1-99,越大越优先)
chrt -f 99 ./market_maker

注意:实时优先级太高可能导致系统卡死。我一般设80-90之间,留点余量给关键系统进程。

3.4 知识体系总览

下面这张图总结了内核调优的核心逻辑:

Linux内核调优核心框架 网络参数调优 • 缓冲区大小(rmem/wmem) • TCP延迟优化(Nagle关闭) • 网卡多队列(RSS/RPS) • 中断亲和性绑定 内存参数调优 • HugePages大页配置 • 关闭透明大页(THP) • mlockall内存锁定 • 关闭swap交换 调度与CPU隔离 • isolcpus核心隔离 • taskset进程绑定 • SCHED_FIFO实时调度 • 中断亲和性设置 最终目标 ✅ 确定性延迟 ✅ 零抖动 ✅ 资源独占

3.5 实战检查清单

每次部署新系统,我都会按这个清单检查一遍:

类别 检查项 推荐值 验证命令
网络 rmem_max 16777216 sysctl net.core.rmem_max
网络 tcp_nodelay 1 sysctl net.ipv4.tcp_nodelay
内存 HugePages数量 ≥16384 grep HugePages /proc/meminfo
内存 THP状态 never cat /sys/kernel/mm/transparent_hugepage/enabled
CPU isolcpus 隔离核心列表 cat /proc/cmdline
CPU 中断亲和性 非隔离核心 cat /proc/irq/*/smp_affinity
调度 进程优先级 SCHED_FIFO 80-99 chrt -p <pid>

核心原则: 调优不是一次性的。每次系统升级、内核更新、硬件变更后,都要重新验证一遍。我见过有人升级内核后忘了重新配置大页,结果延迟从10微秒飙到100微秒。

好了,内核调优这块就聊到这儿。记住一句话:低延迟不是调出来的,是设计出来的。参数只是辅助,架构和代码才是根本。


公众号:蓝海资料掘金营,微信deep3321