DPDK为什么越来越少使用rte_ring?——从Lock-Free到Run-to-Completion,重新理解现代DPDK架构演进(上)

一、一次"架构升级"带来的性能灾难

某运营商核心交换机项目已经稳定运行两年。

系统采用的是典型的流水线架构。

复制代码
RX Thread
    │
    ▼
Parser Thread
    │
    ▼
ACL Thread
    │
    ▼
Forward Thread
    │
    ▼
TX Thread

每一级之间,都通过DPDK提供的rte_ring进行通信。

项目上线初期效果很好。

随着业务增加,团队不断向流水线中增加新的处理模块:

  • VLAN处理
  • ACL
  • QoS
  • NAT
  • Telemetry
  • Flow Statistics
  • 镜像采集

整个系统逐渐演变成:

复制代码
RX
 │
 ▼
Parser
 │
 ▼
Session Lookup
 │
 ▼
ACL
 │
 ▼
QoS
 │
 ▼
Statistics
 │
 ▼
Forward
 │
 ▼
TX

每两个模块之间,都新增一个Ring。

上线半年后。

运营商反馈:

新版本吞吐能力下降约35%。

更加奇怪的是:

所有传统性能指标都没有任何异常。

指标 状态
CPU 100%
RX Drop 0
TX Drop 0
NIC Error 0
ACL耗时 正常
Hash耗时 正常
NUMA 正常
Hugepage 正常

整个系统没有任何异常。

唯一的问题:

就是越来越慢。


二、第一轮排查:是不是算法变复杂了?

首先怀疑ACL。

Perf热点统计:

函数 CPU占比
rte_acl_classify 28%
rte_hash_lookup 11%
Parser 16%
QoS 9%
NAT 13%
其它 23%

和上一版本相比。

几乎没有变化。

说明:

算法没有退化。


继续查看:

Cache Miss。

复制代码
Cache Miss

4.8%

正常。

NUMA:正常。

Hugepage:正常。

NIC:正常。

RX Queue:没有积压。

所有能想到的问题全部排除了。


三、真正奇怪的是:CPU一直很忙,却没有干多少活

工程师继续使用:

复制代码
perf stat

统计CPU事件。

结果发现:

复制代码
Instructions

下降18%

但是:

复制代码
CPU Utilization

100%

继续观察:

复制代码
Cycles

几乎没有变化

也就是说:CPU仍然一直在运行。

但是:真正执行的指令却变少了。

CPU到底在忙什么?

继续分析:

复制代码
perf record

热点开始出现变化。

除了业务函数。

开始出现大量:

复制代码
rte_ring_enqueue_bulk()

rte_ring_dequeue_bulk()

所有人都觉得奇怪。

Ring不是:

Lock-Free。

为什么还能成为热点?


四、Lock-Free,并不代表没有代价

很多刚接触DPDK的人都会认为:

既然官方提供:

复制代码
rte_ring

那么:它一定是最快的。

事实上。Lock-Free只是没有互斥锁(Mutex),并不意味着没有同步成本。

这是很多性能问题产生的根源。

为了理解这一点。

先看看Ring到底是什么。


五、rte_ring到底是什么?

rte_ring本质上就是一个环形队列。

内部维护四个核心变量:

复制代码
prod_head
prod_tail

cons_head
cons_tail

假设:

Ring大小:

复制代码
1024

Producer不断:

复制代码
prod_head++

↓

写入对象

↓

prod_tail++

Consumer不断:

复制代码
cons_head++

↓

读取对象

↓

cons_tail++

整个过程没有任何Mutex。

因此。

很多人认为:

没有锁。

性能一定很好。

实际上。

真正耗时的地方。

恰恰就在:

复制代码
head

tail

这几个变量。


六、为什么Head/Tail会成为性能瓶颈?

继续看Producer。

多个Producer同时写Ring。

例如:

CPU0准备:

复制代码
prod_head

100

↓

101

与此同时。

CPU1也准备:

复制代码
prod_head

100

↓

101

如果:没有保护。Ring立即损坏。

因此。DPDK必须保证:只有一个CPU能够成功修改Head。

于是。源码中大量使用:

复制代码
rte_atomic_compare_exchange()

或者

CAS

也就是:Compare-And-Swap。

很多人看到:CAS。

第一反应:

很快。

实际上。CAS并不是普通CPU指令。

CAS意味着:整个Cache Line。

需要获得:Exclusive Ownership。

也就是说:CPU准备修改:

复制代码
prod_head

之前。

必须先让其它CPU全部失效。

如果:Producer很多。

那么:同一条Cache Line。

就在:

复制代码
CPU0

↓

CPU1

↓

CPU2

↓

CPU3

之间不断迁移。

CPU真正等待的不是CAS。

而是:Cache一致性协议。


七、为什么Ring越多,性能反而越差?

很多项目会认为:模块越细越容易扩展。

于是:

复制代码
RX

↓

Ring

↓

Parser

↓

Ring

↓

Session

↓

Ring

↓

ACL

↓

Ring

↓

QoS

↓

Ring

↓

TX

看起来每个模块都很独立。

实际上,每增加一级。都会新增一次:

复制代码
Enqueue

+

Dequeue

意味着:每个数据包至少增加两次:

  • CAS
  • Memory Barrier
  • Cache同步

如果流水线六级。

那么每个包可能经历:

复制代码
12次

Head/Tail修改

注意。

这里只是:数据包传递。

真正业务处理还没有开始。

很多团队发现:业务模块越拆越细,CPU越来越高,吞吐却越来越低。

原因就在这里。

Ring的成本不是固定的,而是随着模块数量呈线性增长;当数据包处理本身已经很轻量时,线程之间传递数据的成本反而会超过真正的业务逻辑。


八、一个容易被忽略的问题:Memory Barrier

除了CAS之外。

rte_ring还有另一个隐藏成本。

就是:Memory Barrier(内存屏障)。

为了保证:Producer写入对象以后,Consumer一定能够看到。

CPU不能:随意重排序。

因此。每一次:

复制代码
enqueue

dequeue

都会伴随Acquire、Release、Fence。

这些操作不会增加CPU利用率。

但是会降低流水线并行能力。

很多Perf里面:Backend Stall实际上就来自这里。


(未完待续)

相关推荐
在书中成长1 小时前
HarmonyOS 小游戏《对战五子棋》开发第14篇 - 困难AI实现:Minimax算法原理详解
人工智能·算法·harmonyos
飞天狗1 小时前
LCP / INP / CLS 三个维度的实战调优:把 Web Vitals 从不及格拉到优秀的完整路径
前端·性能优化
格子软件1 小时前
GEO系统深度实战:多引擎自适应算法与去中心化流控
人工智能·算法·去中心化·区块链
宝贝儿好2 小时前
【LLM】第三章:BERT讲解+情感分析案例
人工智能·深度学习·神经网络·算法·自然语言处理·bert
初学AI的小高2 小时前
LangGraph 工作流设计:状态图驱动的多 Agent 编排实战
架构
z小猫不吃鱼2 小时前
06 权重幅值剪枝、剪枝后微调与稀疏网络训练:模型剪枝中的三个基本问题
网络·算法·剪枝
2301_780356702 小时前
全视通智慧医院解决方案:构建数智化医疗新生态
大数据·网络·人工智能
QiLinkOS2 小时前
QiLink OS的失败数据共享平台的运作模式是否适用于所有行业?
android·开发语言·人工智能·算法·重构·开源
云栖梦泽2 小时前
从 IMX415 摄像头驱动理解 Media Controller 框架
linux·开发语言·c++·嵌入式硬件