3、操作系统调优:Linux内核参数优化
做高频交易的人都知道,操作系统不是拿来就用的。默认的Linux内核,说白了是为通用场景设计的——它要照顾数据库、Web服务器、桌面用户。但咱们做高频策略,要的是极致的低延迟和确定性。
我个人习惯,每拿到一台新机器,第一件事就是做内核参数调优。这不是锦上添花,而是生死攸关。你想想看,一次上下文切换多花几十微秒,在纳秒级竞争的市场里,这差距足以让订单排到队尾。
CPU隔离与中断亲和性
CPU隔离,就是把某些CPU核心从系统的进程调度中剥离出来。这些核心只跑你的交易策略,别的进程别来打扰。
具体怎么做?我一般在grub启动参数里加isolcpus。比如:
isolcpus=2,3,4,5 nohz_full=2,3,4,5 rcu_nocbs=2,3,4,5
这里把CPU 2-5隔离出来。同时开启nohz_full,减少时钟中断的打扰。rcu_nocbs是把RCU回调也挪走。
taskset或者cset都行。
中断亲和性,这是个容易被忽略的点。网卡中断来了,如果落在你策略所在的核心上,那延迟就炸了。我建议把中断绑定到非隔离核心上。
查看当前中断分布:
cat /proc/interrupts | grep eth0
修改亲和性:
echo 1 > /proc/irq/<中断号>/smp_affinity
这里的1是CPU掩码,表示绑定到CPU 0。如果你有4个核心,掩码分别是1、2、4、8。
内存大页与透明大页
内存分页,默认是4KB一页。对于高频交易来说,这太小了。TLB(页表缓存)很容易被撑爆,导致内存访问变慢。
大页(HugePages)就是解决这个问题的。把页大小改成2MB甚至1GB,TLB的覆盖范围瞬间扩大几百倍。
配置大页:
echo 1024 > /proc/sys/vm/nr_hugepages
这分配了1024个2MB的大页,总共2GB。然后你的程序需要用mmap加上MAP_HUGETLB标志来使用这些大页。
但这里有个坑——透明大页(Transparent HugePages)。
透明大页是内核自动把普通内存页合并成大页。听起来很美,但实际在高频场景下是个灾难。为什么?因为它会在后台做内存规整(compaction),这个过程会触发大量页面迁移,导致不可预测的延迟。
关闭方法:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag
我个人习惯,在/etc/rc.local里加上这两行,确保重启后也生效。
文件系统选择:XFS vs EXT4
高频交易对文件系统的要求很明确:低延迟、高并发、日志开销小。
EXT4是老牌选手,稳定可靠。但它的日志机制(journal)在高频写入场景下,会有一定的性能开销。尤其是当你有大量小文件操作时,EXT4的锁竞争会比较明显。
XFS是后起之秀,设计上更注重大文件和高并发。它的分配组(allocation group)机制,允许多个线程同时写入不同区域,锁冲突少很多。
| 特性 | EXT4 | XFS |
|---|---|---|
| 最大文件大小 | 16TB | 8EB |
| 最大文件系统 | 1EB | 8EB |
| 日志开销 | 中等 | 低 |
| 高并发写入 | 一般 | 优秀 |
| 碎片整理 | 支持 | 支持 |
我的建议:如果只是存日志和配置文件,EXT4完全够用。但如果你的策略需要高频写入行情数据或订单记录,XFS会是更好的选择。
noatime和nodiratime参数,可以避免每次访问都更新时间戳,减少不必要的磁盘写入。
网络协议栈调优
网络延迟是高频交易的核心瓶颈之一。Linux默认的网络协议栈,是为吞吐量优化的,不是为延迟优化的。
首先,调整网卡ring buffer。这个缓冲区如果太小,丢包;太大,延迟高。我一般设成2048或4096:
ethtool -G eth0 rx 4096 tx 4096
其次,开启RPS(Receive Packet Steering)和RFS(Receive Flow Steering)。这两个机制可以把网络中断分散到多个CPU核心上,避免单个核心成为瓶颈。
echo f > /sys/class/net/eth0/queues/rx-0/rps_cpus
echo 4096 > /sys/class/net/eth0/queues/rx-0/rps_flow_cnt
这里f表示使用CPU 0-3。
还有,调整TCP参数。高频交易通常用UDP,但如果你用TCP做行情或订单传输,这些参数就很重要:
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
这些参数说白了就是:加大缓冲区,减少TIME_WAIT状态的连接数,让TCP更快地回收资源。
tcp_tw_reuse和tcp_tw_recycle容易混淆。我建议只用tcp_tw_reuse,别碰tcp_tw_recycle。后者在NAT环境下会导致严重问题,我踩过这个坑。
最后,如果你追求极致性能,可以考虑使用DPDK(Data Plane Development Kit)。它绕过内核协议栈,直接在用户态处理网络包。延迟可以从几十微秒降到几微秒。
但DPDK也有代价——你需要自己实现TCP/IP协议栈,或者用现成的库如libmoon、f-stack。我个人只在最核心的行情接收链路上用DPDK,其他场景还是用内核协议栈,毕竟维护成本低很多。