接上文DPDK为什么越来越少使用rte_ring——从Lock-Free到Run-to-Completion重新理解现代DPDK架构演进上-CSDN博客九、打开源码看看rte_ring到底做了什么很多人都知道rte_ring_enqueue_bulk() rte_ring_dequeue_bulk()但是很少有人真正读过它的源码。如果打开lib/ring/rte_ring_elem_pvt.h会发现无论是 Producer 还是 Consumer整个流程都遵循同样的步骤申请空间 │ ▼ 更新 Head │ ▼ 写入对象 │ ▼ Memory Barrier │ ▼ 更新 Tail看起来非常简单。真正复杂的是第二步更新 Head。十、为什么 Head 更新需要 CAS假设 Ring 当前状态Head 100 Tail 80CPU0准备放入32个对象。与此同时CPU1也准备放入16个对象。如果两个CPU同时执行Head 32那么Ring马上就损坏了。因此DPDK必须保证只有一个CPU能够成功修改prod_head源码最终会进入类似下面的逻辑为了说明原理省略细节do { old_head prod_head; new_head old_head n; } while (!CAS(prod_head, old_head, new_head));CAS失败。继续重试一直成功为止。十一、CAS真正慢在哪里很多资料都会说CAS是一条CPU原子指令。于是很多人认为一条指令还能慢事实上CAS真正耗时的不是执行。而是获得Cache Line所有权。例如CPU0 持有 prod_head这时候CPU1准备修改prod_headCPU必须首先执行Invalidate ↓ CPU0 Cache失效 ↓ CPU1获得Exclusive状态 ↓ 才能CAS如果Producer数量很多。整个Cache Line就会不停CPU0 ↓ CPU2 ↓ CPU1 ↓ CPU3 ↓ CPU0不停迁移。MESI协议开始疯狂工作。真正消耗时间的是Cache Coherency。不是CAS本身。十二、为什么 MP/MC 比 SP/SC 慢很多DPDK提供四种Ring模式。模式ProducerConsumerSP/SC单Producer单ConsumerSP/MC单Producer多ConsumerMP/SC多Producer单ConsumerMP/MC多Producer多Consumer很多项目为了以后方便扩展。直接全部使用RING_F_MP_HTS_ENQ RING_F_MC_HTS_DEQ认为以后加线程不用改。实际上这是性能的大坑。因为SP模式Producer永远只有一个根本不用CAS。直接prod_head n;即可。而MP每一次都需要CAS ↓ Retry ↓ Memory Barrier如果你的系统实际上只有一个RX线程 一个Dispatcher那么继续使用MP完全是在给CPU增加负担。十三、为什么流水线越长Ring成本越高继续回到事故现场。系统流水线RX ↓ Parser ↓ Session ↓ ACL ↓ QoS ↓ Forward ↓ TX一共六级。每一级都有Enqueue Dequeue如果每个包需要一次CAS Fence Cache同步那么六级流水线。意味着一个包至少经历12次 同步注意。这里没有任何业务逻辑。只是线程之间传递。真正开始ACL之前CPU已经花掉大量时间。在把包交给别人。十四、为什么现代DPDK越来越回归RTC越来越多的新项目开始采用RX ↓ Parser ↓ Session ↓ ACL ↓ QoS ↓ Forward ↓ TX全部一个Worker完成。整个过程中没有任何Ring。没有任何线程切换。数据始终停留当前CPU。这样带来的收益第一Session一直在当前L1。第二Parser得到的数据ACL立即使用。第三NAT修改Forward立即发送。整个Working Set始终停留CPU Cache。这就是Run-to-Completion最大的优势。十五、那Dispatcher为什么还存在很多人看到RTC。马上问那为什么还需要Dispatcher因为NIC只有RX Queue。不知道Session属于谁。Dispatcher真正做的事情只有RX Burst ↓ Parser ↓ Lookup Session ↓ 找到Owner ↓ 投递一次注意。这里只有一次Ring。而不是Parser Ring ACL Ring QoS Ring TX Ring现代DPDK项目。一般只有Dispatcher ↓ Worker这一层通信。后面全部RTC。因此每个包最多一次Enqueue一次Dequeue。相比流水线减少80%以上同步成本。十六、为什么你的UPF设计也是这样如果回顾我们整个UPF架构就会发现N3 Dispatcher负责RX BurstGTP-U解析TEID查找Session Lookup找到Owner Worker之后整个数据包都会交给Owner Worker。Worker内部依次完成PDR ↓ FAR ↓ QER ↓ URR ↓ NAT ↓ 封装 ↓ TX整个过程。不会再经过任何Ring。这也是为什么我们之前确定的设计基准是Dispatcher负责找到谁处理Worker负责把事情处理完。而不是Dispatcher ↓ Parser Worker ↓ ACL Worker ↓ NAT Worker ↓ TX Worker后者虽然模块化。但是每一级都需要同步。十七、什么时候应该使用rte_ring看到这里。有人可能会认为Ring是不是应该完全不用当然不是。Ring仍然非常重要。例如控制面PFCP ↓ Control Thread ↓ Worker日志Worker ↓ Logger ThreadTelemetryWorker ↓ Statistics Thread这些都是低频通信。Ring非常合适。真正不适合的是100Mpps 数据面如果每秒一亿个包全部经过五级Ring。那么CPU真正处理业务的时间反而越来越少。十八、性能测试对比某实验室分别实现两种架构在相同硬件、相同ACL规则、相同报文模型下进行了测试。指标多级Ring流水线Dispatcher RTCRing数量61CAS次数每包122Memory Barrier每包122L3 Cache Miss8.9%3.2%Backend Stall41%18%吞吐58 Gbps94 GbpsCPU利用率100%100%这里最容易误解的一点是CPU利用率始终都是100%。提升性能的原因不是CPU更空闲而是CPU花在Cache一致性原子同步Memory Barrier线程间传递上的时间大幅减少。因此相同100%的CPU。真正执行业务逻辑的比例更高。十九、现代DPDK架构演进的本质过去十几年DPDK架构经历了一个明显的演进过程早期阶段强调模块化倾向于多级流水线通过rte_ring将各功能模块解耦。中期阶段发现线程同步和缓存一致性成为新的瓶颈开始减少流水线层级。当前主流采用 Dispatcher Owner Worker Run-to-Completion尽量让一个数据包在同一个CPU核心完成全部处理。这并不是因为rte_ring设计不好而是因为随着CPU核心数不断增加跨核心同步的成本增长速度已经超过了很多网络业务本身的计算成本。二十、写在最后rte_ring是DPDK中最优秀的基础组件之一它提供了高效、可靠的无锁队列实现了多线程之间的数据交换。但是它解决的是线程之间如何安全通信而不是数据面如何获得最高吞吐。对于现代高性能数据面来说更重要的问题已经变成如何减少跨CPU缓存迁移如何降低Memory Barrier带来的流水线停顿如何保持Session始终由唯一Worker拥有如何让一个数据包尽可能在一个CPU核心内完成整个生命周期。因此现代DPDK架构的演进方向并不是不用Ring而是只在真正需要跨线程通信的地方使用Ring让绝大多数数据包处理逻辑在Owner Worker内Run-to-Completion完成。这也是为什么今天越来越多的高性能交换机、UPF、vRouter、DPI和网络安全设备都在采用轻Dispatcher RTC Worker架构的根本原因。它不是一种编码风格而是顺应现代CPU缓存层次结构和多核架构演进的必然选择。