Skip to content

通用网卡通信时延优化方案 ​

本文面向对通信时延和抖动敏感的网卡业务场景,例如高频交易、实时音频、工业控制、云游戏、运动控制和低延迟数据采集等。时延优化的目标是缩短数据从应用到网卡、从网卡到应用的路径,并减少调度、中断、排队和协议栈处理带来的等待时间。

需要注意:低时延优化经常与高吞吐优化存在取舍。例如增大 GRO 聚合数、增大缓冲区、提高中断聚合程度可能提升吞吐,但会增加等待时间。因此实际调优时应先明确业务目标:低时延优先、吞吐优先,还是二者平衡。

优化思路 ​

方向目标常见手段
缩短路径减少协议栈处理开销AF_PACKET 旁路、驱动轮询、减少中间队列
减少调度抖动降低线程迁移和抢占线程绑核、提高优先级
减少等待降低中断和聚合等待减小中断聚合、降低 GRO 聚合数
降低排队避免大缓冲堆积控制 socket 缓冲区、ring 深度和应用处理速率
稳定 CPU 资源保障关键线程运行将业务线程放到相对空闲 CPU 核

发送方向优化 ​

使用 AF_PACKET 旁路协议栈 ​

如果业务报文可以直接以二层以太网帧形式发送,可通过 AF_PACKET 接口绕过传统协议栈路径,将业务报文直接发送到设备二层处理单元。

适用场景:

  • 自定义二层协议;
  • 对 UDP/TCP 协议栈能力依赖较少;
  • 对发送路径确定性要求高;
  • 业务自身可处理报文格式、校验和、重传或可靠性策略。

注意:旁路协议栈会降低通用协议能力,需要业务自行承担更多报文处理逻辑。

发送线程绑核 ​

将发送线程绑定到相对空闲的 CPU 核,减少线程在不同核心之间迁移带来的 cache 失效和调度抖动。

bash
# 4010032 为 thread id,3 为 cpu id
affinity 4010032 3

建议:

  • 将发送线程、相关业务计算线程尽量放在固定核心或相邻核心。
  • 避免与高负载网络协议栈线程、中断线程、后台任务抢同一个核心。
  • 调优前后记录平均时延和 P99/P999 抖动。

提高发送线程优先级 ​

提高发送线程优先级,让 CPU 更优先处理报文发送逻辑。

bash
# 15 为优先级,4010032 为 thread id
sprio 15 4010032

建议:

  • 优先级不宜盲目调到最高,避免影响系统关键线程。
  • 与绑核配合使用效果更稳定。
  • 调整后需要观察系统整体响应和其他实时任务是否受影响。

接收方向优化 ​

接收线程与应用线程绑核 ​

将接收线程绑定到固定 CPU 核,并将消费该数据的应用线程绑定到同一核心或相邻核心,减少跨核迁移和数据 cache 迁移成本。

bash
# 401002c 为接收线程 id,3 为 cpu id
affinity 401002c 3

如果网卡驱动、中断线程、协议栈线程可识别,建议将关键接收路径上的线程放到固定核心,并避开其他高负载任务。

提高接收线程优先级 ​

提高接收线程优先级可以减少报文到达后等待调度的时间。

bash
# 15 为优先级,401002c 为接收线程 id
sprio 15 401002c

该方法适合报文频率较高但每包处理量较小的业务。若应用处理逻辑较重,应同时优化应用线程优先级和处理路径。

使用 AF_PACKET 接收 ​

如果业务可直接处理二层报文,可通过 AF_PACKET 将报文直接送到应用,减少协议栈解析、分发和排队过程。

适用场景:

  • 自定义二层协议;
  • 低延迟行情、采集、控制报文;
  • 应用侧可直接解析以太网帧;
  • 不依赖 TCP/UDP socket 语义。

使用驱动轮询 ​

如果驱动支持轮询模式,可由应用主动调用轮询服务函数从驱动抓取报文,规避中断等待和中断调度开销。

常用函数:

  • netdev_poll_enable
  • netdev_poll_svc

示例:

c
int main(int argc, char **argv)
{
    static char *if_name = NULL;
    struct netdev *pNetDev = NULL;
    int ret;

    if_name = argv[1];
    pNetDev = netdev_find_by_ifname(if_name);

    ret = netdev_poll_enable(pNetDev, poll_rcv, NULL);
    if (ret) {
        return ret;
    }

    while (1) {
        netdev_poll_svc(pNetDev);
    }

    return 0;
}

轮询模式可降低接收等待时间,但通常会提高 CPU 占用。适合低延迟优先且可独占部分 CPU 资源的业务。

降低中断聚合等待 ​

如果驱动支持中断聚合,可降低聚合个数或缩短聚合时间,减少报文在网卡或驱动中等待“凑批”的时间。

注意:降低中断聚合会显著提高中断频率和 CPU 占用,可能影响系统吞吐和其他任务。建议结合 CPU 余量和业务时延目标逐步调整。

与吞吐量优化的取舍 ​

低时延优化和吞吐量优化常见冲突如下:

配置对吞吐量对时延
增大 GRO 聚合数通常提升增加等待和抖动
增大 socket 缓冲区减少断流增加排队延迟
增大 RX/TX 描述符吸收突发流量可能增加排队深度
提高中断聚合降低 CPU 中断压力增加报文等待时间
轮询接收可降低等待CPU 占用升高
线程绑核/提优先级稳定关键路径可能挤占其他任务

推荐调优流程 ​

  1. 建立基线:记录平均时延、P99/P999 时延、CPU 占用、吞吐量和丢包。
  2. 固定关键线程:对发送线程、接收线程和应用处理线程进行绑核。
  3. 提高关键线程优先级:优先保障收发路径线程调度。
  4. 减少协议栈路径:评估是否使用 AF_PACKET 或驱动轮询。
  5. 降低等待类配置:减小中断聚合、降低 GRO 聚合数、控制缓冲区和 ring 深度。
  6. 复测吞吐影响:低延迟配置完成后,确认吞吐仍满足业务最低要求。
  7. 长期稳定性验证:进行长时间压测,观察时延抖动、丢包、CPU 占用和系统稳定性。

验证指标 ​

低时延场景不应只看平均值,建议同时关注:

指标说明
平均时延基础参考值,但不能代表极端抖动
P99 / P999衡量尾延迟,对实时业务更关键
最大时延判断是否存在异常调度或中断阻塞
抖动反映时延稳定性
CPU 占用判断是否因轮询或高频中断导致资源耗尽
丢包/重传判断低延迟配置是否破坏链路稳定性

经验结论 ​

低时延优化的关键不是单纯打开某个开关,而是让报文路径更短、关键线程更稳定、等待队列更少。对于极低延迟场景,可优先考虑绑核、线程优先级、AF_PACKET、轮询接收和降低中断聚合;对于吞吐和低延迟都重要的场景,应保留适度聚合和缓冲,避免 CPU 被中断打满。