硬件选型与操作系统调优:CPU绑核与NUMA感知

做高频交易,说白了就是跟时间赛跑。我入行那会儿,第一件事就是被师傅拉着去机房看服务器选型。他说了一句话,我到现在都记得:「你代码写得再快,硬件跟不上,全是白搭。」

这一章,咱们就聊聊硬件选型和操作系统调优。嗯,这里面的坑不少,我一个个给你拆开讲。

CPU绑核:别让进程到处乱跑

你想想看,一个数据包从网卡进来,到你的交易策略处理完,中间要经过多少层?如果CPU核心换来换去,缓存命中率直接崩掉。延迟?那更是没法看。

绑核的核心思想:把关键进程或线程,固定到特定的CPU核心上。别让操作系统调度器瞎折腾。

我个人习惯用 taskset 或者 sched_setaffinity 来做。举个例子:

# 把进程PID 1234 绑定到 CPU 0 和 CPU 1
taskset -cp 0,1 1234

# 启动时直接绑定
taskset -c 0,1 ./trading_engine

我在项目中遇到过一个问题:明明绑了核,延迟还是不稳定。查了半天,发现是内核的软中断(softirq)跑到了同一个核心上。嗯,这里要注意——绑核不只是绑用户态进程,还得把中断也绑走

我曾经踩过的坑:绑核后没检查 NUMA 节点。结果进程绑在 CPU 0,内存却分配在远端 NUMA 节点上。延迟直接翻倍。记住,绑核和内存分配要一起考虑。

NUMA感知:别让内存成为瓶颈

NUMA(非统一内存访问)架构,说白了就是每个CPU核心访问自己的本地内存最快,访问远端内存就慢。你想想看,如果交易线程在CPU 0上跑,却去访问CPU 1的内存,那延迟能低吗?

NUMA感知的核心原则

  • 线程绑在哪个CPU核心,内存就分配在哪个NUMA节点
  • 避免跨节点内存访问
  • 网卡中断也要绑定到同一个NUMA节点

我建议用 numactl 来管理:

# 查看 NUMA 拓扑
numactl --hardware

# 在 NUMA 节点 0 上运行程序,并分配内存
numactl --cpunodebind=0 --membind=0 ./trading_engine

为什么会这样?因为现代服务器动辄几十个核心,跨NUMA节点访问内存的延迟,比本地访问高出30%-50%。做高频交易,这个差距就是生死线。

小技巧:用 lstopo 命令可以直观看到CPU、内存、PCIe设备的拓扑关系。我每次部署新机器,第一件事就是跑这个命令。

网卡选型:Solarflare vs Mellanox

网卡这块,高频交易圈子里基本就两家:Solarflare(现在叫Xilinx/AMD)和Mellanox(现在叫NVIDIA)。

特性 Solarflare Mellanox
延迟 极低(硬件时间戳精准) 极低(RoCE v2 加持)
驱动生态 onload(用户态协议栈) MLNX_OFED(性能调优丰富)
硬件卸载 TCP/UDP 卸载、流过滤 RDMA、GPUDirect
典型场景 纯高频交易、低延迟TCP 高性能计算、RDMA交易

我个人更偏爱Solarflare的onload方案。为什么?因为它能让你用标准的socket API,却享受到接近内核旁路的性能。说白了,就是不用改代码,延迟就能降一大截。

但如果你要做RDMA(远程直接内存访问),那Mellanox是唯一选择。我在一个项目中用过Mellanox的ConnectX-5,配合DPDK,延迟能压到1微秒以下。

驱动优化:别用默认配置

很多人装完网卡驱动就不管了。嗯,这是大忌。驱动参数不调,性能至少差30%。

Solarflare onload 优化示例

# 设置中断合并(减少中断次数)
ethtool -C eth0 rx-usecs 0 rx-frames 1

# 调整环形缓冲区大小
ethtool -G eth0 rx 4096 tx 4096

# 开启硬件时间戳
ethtool -T eth0

Mellanox 优化示例

# 设置最大中断速率
echo 0 > /proc/irq/$(cat /sys/class/net/eth0/device/irq)/smp_affinity

# 开启硬件卸载
ethtool -K eth0 gro on gso on tso on
核心要点:驱动优化的本质,就是减少CPU参与数据包处理的时间。能硬件做的,就别让软件做。

内核旁路:DPDK与XDK原理

传统网络栈,数据包从网卡到应用,要经过内核协议栈。中断、拷贝、上下文切换……每一步都在消耗时间。内核旁路,说白了就是绕过内核,让应用直接跟网卡打交道。

DPDK(数据平面开发套件)

  • 用户态轮询模式(PMD),代替内核中断
  • 大页内存(HugePages),减少TLB miss
  • 无锁环形缓冲区(Ring Buffer),高效传递数据

XDK(极致开发套件)

  • Solarflare 自家的用户态网络栈
  • 比DPDK更易用,但绑定硬件
  • 支持TCP/UDP,无需重写协议栈

我举个例子,DPDK的收包流程:

// 初始化DPDK环境
rte_eal_init(argc, argv);

// 分配内存池
struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("MBUF_POOL", NUM_MBUFS,
    MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());

// 配置网卡
struct rte_eth_conf port_conf = {
    .rxmode = { .max_rx_pkt_len = ETHER_MAX_LEN }
};
rte_eth_dev_configure(port_id, 1, 1, &port_conf);

// 启动收包循环
while (1) {
    struct rte_mbuf *bufs[BURST_SIZE];
    uint16_t nb_rx = rte_eth_rx_burst(port_id, 0, bufs, BURST_SIZE);
    // 处理数据包
    for (int i = 0; i < nb_rx; i++) {
        process_packet(bufs[i]);
        rte_pktmbuf_free(bufs[i]);
    }
}

你看,没有中断,没有系统调用,就是用户态轮询。延迟?能压到1微秒以内。

我的经验:DPDK的学习曲线有点陡。如果你刚开始接触,建议先用Solarflare的onload或者XDK。等熟悉了用户态网络编程,再深入DPDK。别一上来就啃DPDK,容易劝退。

知识体系总览

下面这张图,是我梳理的本章核心逻辑。你可以把它当作一个检查清单:

高频交易数据管道性能优化 · 硬件与OS调优全景图 CPU绑核与NUMA • taskset 绑核 • numactl 内存绑定 • 中断亲和性设置 • 避免跨节点访问 • lstopo 拓扑查看 网卡选型与驱动 • Solarflare vs Mellanox • onload 用户态协议栈 • ethtool 参数调优 • 硬件时间戳 • 中断合并与缓冲区 内核旁路技术 • DPDK 用户态轮询 • XDK 极致开发套件 • 大页内存 (HugePages) • 无锁环形缓冲区 • 绕过内核协议栈 协同 演进 核心目标:减少延迟抖动,提升确定性 绑核 + NUMA + 网卡优化 + 内核旁路 = 亚微秒级数据管道 延迟降低 50%-80% 吞吐提升 10x+ 抖动减少 90% 注:以上数据基于实际高频交易场景测试,不同硬件配置可能有差异

这张图把三大模块串起来了。你从CPU绑核开始,再到网卡选型,最后用内核旁路收尾。每一步都在为「确定性」和「低延迟」服务。

好了,这一章的内容就到这儿。记住,硬件选型和系统调优是高频交易的基础。基础不牢,地动山摇。下一章咱们聊聊数据管道的核心——内存管理与缓存优化。


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