为什么Burst越大,时延反而越高?——一次P99.99时延飙升事故,彻底理解DPDK Burst处理模型(上)

📅 2026/7/9 3:51:46
为什么Burst越大,时延反而越高?——一次P99.99时延飙升事故,彻底理解DPDK Burst处理模型(上)
一、一次性能优化引发的现网投诉某运营商5G核心网UPF已经稳定运行近一年。整个系统采用典型的DPDK Run-to-Completion模型。每个Worker线程固定绑定一个CPU核心负责RX收包GTP-U解析Session查找PDR/FAR处理NAT封装TX发送业务长期保持稳定。为了进一步提升吞吐研发团队对数据面进行了一个看似非常合理的优化。原来的代码#define MAX_PKT_BURST 32 nb_rx rte_eth_rx_burst(port_id, queue_id, rx_pkts, MAX_PKT_BURST);修改为#define MAX_PKT_BURST 128 nb_rx rte_eth_rx_burst(port_id, queue_id, rx_pkts, MAX_PKT_BURST);优化理由十分充分一次收更多包减少rte_eth_rx_burst()调用次数减少循环判断提高CPU执行效率增加Cache命中率。实验室测试结果也符合预期。指标修改前修改后PPS64.8 Mpps66.3 Mpps吞吐91.8 Gbps93.6 GbpsCPU100%100%大家一致认为这是一次成功的优化。版本顺利上线。二、吞吐提升了投诉却来了上线后的第二天。运营商网络维护中心开始收到业务告警。奇怪的是。投诉内容并不是网络中断丢包吞吐不足。而是VoNR偶尔出现卡顿。与此同时HTTPS页面偶尔需要等待一两秒。数据库短连接偶尔超时。TCP重传率没有明显变化。UDP没有丢包。系统所有传统指标几乎全部正常。例如指标状态CPU100%RX Drop0TX Drop0NIC Error0PMD线程正常HugePage正常NUMA正常甚至整体吞吐相比以前还提高了约2%。所有人都开始困惑。为什么性能更高了用户体验反而更差了三、真正异常的是P99.99团队开始查看更细粒度的监控。首先平均时延。修改前 18 us 修改后 19 us几乎没有变化。继续P95修改前 24 us 修改后 26 us依旧正常。然后P99修改前 38 us 修改后 165 us开始明显增加。继续P99.9修改前 62 us 修改后 740 us所有人开始紧张。最后P99.99修改前 118 us 修改后 7.6 ms整个会议室安静了。原来真正的问题并不是平均时延。而是Tail Latency长尾时延。绝大多数包仍然处理得很快。只有极少部分包等待了几毫秒。对于网页影响不明显。但是对于VoNR一两个包延迟几毫秒已经足够产生卡顿。四、是不是CPU已经处理不过来了看到时延增加。第一反应自然还是CPU。于是继续Perf。热点统计函数CPU占比Parser17%Session Lookup13%PDR/FAR26%NAT11%TX18%其它15%和升级之前。几乎完全一致。继续Cache Miss4.1%正常。Backend Stall18%正常。NUMA正常。Hugepage正常。PCIe正常。网卡正常。整个系统没有任何性能瓶颈。于是问题再次陷入僵局。五、直到有人提出一个问题会议持续了几个小时。大家不停排查NICCPUACLSessionNAT。最后。一位老工程师问了一句我们最近是不是改过Burst大小所有人愣了一下。因为Burst一直被认为只是一次多收几个包。怎么可能影响时延事实上真正的问题。正是Burst。六、为什么DPDK一定要Burst很多刚学习DPDK的人都会问为什么Linuxrecv()一次一个包。DPDK却一定要rte_eth_rx_burst()一次几十个包。这是因为现代CPU真正昂贵的不是计算。而是进入一次数据处理流程。例如CPU一次执行读取RX Tail ↓ 读取Descriptor ↓ DMA同步 ↓ 更新Head ↓ 返回如果每个包都执行一次。那么CPU的大量时间都会消耗在循环、函数调用、分支预测、Doorbell访问、Descriptor更新。而不是真正处理数据包。因此DPDK选择批处理。例如一次32个包整个流程只执行一次。CPU效率立即提升。这就是Burst存在的意义。七、Burst真正优化了什么很多人认为Burst只是减少函数调用。实际上远不止如此。一个RX Burst实际上完成了很多工作NIC Descriptor Ring ↓ 检查DD Bit ↓ 批量读取Descriptor ↓ DMA同步 ↓ 更新RX Tail ↓ 返回mbuf数组注意这里Descriptor本身就是顺序存放。CPU读取32个。几乎全部命中L1/L2 Cache。PCIe DMA也是连续访问。因此Burst不仅减少函数调用。更重要的是提升Descriptor访问效率Cache局部性DMA连续性指令流水线利用率。这也是为什么几乎所有Intel官方Sample。默认#define MAX_PKT_BURST 32而不是1。八、那为什么128反而会增加时延到这里。看起来Burst越大应该越快。为什么128却导致P99.99增加几十倍答案藏在一个很多DPDK开发者容易忽略的现象中第一个包必须等待最后一个包。假设一次收到128个包。Worker不会收到一个处理一个。而是RX Burst(128) ↓ for(i0;i128;i) ↓ 全部处理完成 ↓ TX Burst于是。第1个包虽然最早到达却必须等待后面127个包全部完成才能一起发送。这种等待就是后面要分析的Head-of-Line Blocking队首阻塞。它也是本次事故真正的根源。未完待续