在我最开始做DPDK开发时,我曾遇到一个非常诡异的问题:

程序运行正常,吞吐也不错,但每隔几秒钟就会出现一次明显性能下降:

  • PPS 突然掉一半
  • 时延明显增加
  • 几百毫秒后又恢复正常

这种现象不是持续性的,而是周期性抖动。

更奇怪的是:

  • CPU 没打满
  • 网卡无丢包
  • 代码没有锁竞争
  • 流量模型也很稳定

最后定位下来,问题根源只有两个字:NUMA

而理解这个问题的过程,也让我真正理解了 DPDK 高性能架构中最容易被忽视的一层。


一、问题现场

测试环境:服务器配置

双路 Xeon
2 个 CPU socket
64 核
双口 25G 网卡

程序架构:

  • 4 个 RX 核
  • 4 个 worker 核
  • 2 个 TX 核

理论上非常正常。

实际现象:压测时:平均 12 Mpps

但每隔几秒:掉到6 Mpps

持续约:300~500 ms 然后恢复。

这种抖动在网络设备里很危险。

二、第一反应:是不是代码有锁?

很多人遇到性能抖动,第一反应是:某处锁竞争。

检查:

  • spinlock
  • rte_ring
  • stats
  • timer

结果:没有明显问题。

三、第二反应:是不是 cache miss

继续 perf 分析:

发现:偶尔会出现大量:

LLC miss
remote access

这里开始有线索。

四、NUMA 是什么

NUMA 全称:Non-Uniform Memory Access

即:非一致性内存访问架构。

在双路服务器中:每颗 CPU 都有自己的本地内存控制器。

结构类似:

https://images.openai.com/static-rsc-4/qgFUFoAlRa7BaFiR6dSDHhbf6y40gW5gSAyc2FlTVgykmAUhBItFg1ptZ4qIO6IH5g3spRaF91AsskG7_6tQGj0GiFXrU7SYBauX1i7M_CX6qWsSHZDmPSV_lf37G65QVZQ3-veQWQRbTpuMDZw9WGuRCm-PrdjMH5DNFEgK1Rxj5SfkSe21cYH43PT0nI_9?purpose=fullsize

https://images.openai.com/static-rsc-4/FKBST4cL7MHnUxtgem0UTIDl30jvE0-XQZQImQSo3m7-S-o2ql4iO_vU3aNUsjV5R1jqD6pqHcq6SG_4MvQUTwbH4eKpOtgLyyVmVxnMT95zdsMgLYHOXpxOaHdlVMe9O3BrlAgccmdpe4tkxzzwiSMfKh7k2h-vlodLT_6nKFk7V3bObzjIibIu8DbN2Efj?purpose=fullsize

https://images.openai.com/static-rsc-4/PE6CAubIRG9GqHQyKmmerqt_KYeViK_qTFZ6Fdug5d-bf6Eq_BqP4qhp-HGe_21JKUdJqLmcz3GrVYC1dEmziiaKGqjafaPZlEFw1iDnjBiflB-wBw4x9L5lAksdiz3LeLTAlIeSJHj0xrbssy6bRkh2qe92H158LgfjjlWZtKXGboNWwOx_C2vlF6xEWuTX?purpose=fullsize


socket0 ---> local memory0
socket1 ---> local memory1

每颗 CPU 访问自己的内存更快。访问另一颗 CPU 的内存更慢。

五、为什么会影响 DPDK

DPDK 大量依赖:

  • hugepage
  • mempool
  • mbuf

这些对象都在内存中。

如果:网卡在 socket0,但 worker 跑在 socket1,

就会出现:

NIC -> socket0 memory -> socket1 CPU

跨 NUMA 访问。

六、为什么会“抖动”,不是一直慢?

这是最迷惑人的地方。

原因在于:缓存掩盖了问题。

正常阶段

当热点数据仍在 cache 中:访问快。

某个时刻

cache line 被淘汰:CPU 需要重新访问远端内存。

于是:延迟突增,表现为抖动。

七、如何确认 NUMA 问题

先查看网卡在哪个 NUMA 节点。

命令:

cat /sys/class/net/eth0/device/numa_node

输出:

0

说明网卡在:socket0。

再看线程绑核:

lscpu -e

发现:

worker 被绑在:

socket1

问题找到了。

八、一个关键知识:hugepage 也有 NUMA 属性

很多人只知道 hugepage。

但忽略了:hugepage 是分 NUMA 节点的。

例如:

cat /sys/devices/system/node/node0/hugepages/...
cat /sys/devices/system/node/node1/hugepages/...

每个 node 独立。

如果分配不当:即使线程在本地,内存也可能在远端。

九、mempool 为什么更敏感

每个包对应一个:rte_mbuf

来自:DPDK Mempool

如果 mempool 在远端:每个收包都跨节点。代价非常高。

十、真实错误配置

当时代码:

mbuf_pool = rte_pktmbuf_pool_create(...);

没有指定 socket:

默认:

SOCKET_ID_ANY

于是:可能分配在错误节点。

十一、正确做法

应该:

rte_socket_id()

或者明确:

socket_id = rte_lcore_to_socket_id(lcore_id);

然后:

rte_pktmbuf_pool_create(..., socket_id);

保证本地分配。

十二、另一个隐藏坑:ring 也有 NUMA

不仅 mbuf。

连:DPDK Ring 也有 NUMA 属性。

创建时:

rte_ring_create(...)

如果 socket 配错:跨核通信同样变慢。

十三、优化后结果

调整:

1. 网卡对应核绑定

全部绑到:socket0。

2. mempool 本地分配

按 lcore 创建。

3. ring 本地创建

同 socket。

优化后:

性能

从:12 Mpps 波动 变成:16 Mpps 稳定

并且:抖动消失。

十四、为什么 NUMA 在 DPDK 中格外重要

因为 DPDK 是:内存带宽型程序

大量操作:

  • mbuf
  • descriptor
  • ring
  • flow table

一旦远端访问:代价会被放大。

十五、如何避免 NUMA 问题

工程建议非常明确。

1. 网卡和核心同节点

必须。

2. hugepage 按节点预留

例如:

echo 1024 > node0/hugepages/...

3. per-socket mempool

推荐。

4. per-socket ring

推荐。

5. 不跨 socket 传递热点对象

尤其:mbuf。

十六、一个经验法则

如果是双路服务器:

最佳实践:socket0 处理 port0。socket1 处理 port1。

不要跨节点。

即:

port0 -> socket0
port1 -> socket1

最优。

十七、总结

这次问题看似只是:周期性性能抖动

但本质暴露了高性能系统最重要的一点:内存位置比代码本身更重要。

通过这个问题,我们理解了 DPDK 中几个非常关键的概念:

包括

  • NUMA
  • hugepage
  • mempool
  • mbuf
  • ring
  • remote memory access

很多时候,性能瓶颈并不是算法。

而是:数据放错了地方。

这也是高性能数据面开发和普通应用开发最大的区别之一。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐