一、一次架构升级带来的性能灾难某运营商核心交换机项目已经稳定运行两年。系统采用的是典型的流水线架构。RX Thread │ ▼ Parser Thread │ ▼ ACL Thread │ ▼ Forward Thread │ ▼ TX Thread每一级之间都通过DPDK提供的rte_ring进行通信。项目上线初期效果很好。随着业务增加团队不断向流水线中增加新的处理模块VLAN处理ACLQoSNATTelemetryFlow Statistics镜像采集整个系统逐渐演变成RX │ ▼ Parser │ ▼ Session Lookup │ ▼ ACL │ ▼ QoS │ ▼ Statistics │ ▼ Forward │ ▼ TX每两个模块之间都新增一个Ring。上线半年后。运营商反馈新版本吞吐能力下降约35%。更加奇怪的是所有传统性能指标都没有任何异常。指标状态CPU100%RX Drop0TX Drop0NIC Error0ACL耗时正常Hash耗时正常NUMA正常Hugepage正常整个系统没有任何异常。唯一的问题就是越来越慢。二、第一轮排查是不是算法变复杂了首先怀疑ACL。Perf热点统计函数CPU占比rte_acl_classify28%rte_hash_lookup11%Parser16%QoS9%NAT13%其它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大小1024Producer不断prod_head ↓ 写入对象 ↓ prod_tailConsumer不断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意味着每个数据包至少增加两次CASMemory BarrierCache同步如果流水线六级。那么每个包可能经历12次 Head/Tail修改注意。这里只是数据包传递。真正业务处理还没有开始。很多团队发现业务模块越拆越细CPU越来越高吞吐却越来越低。原因就在这里。Ring的成本不是固定的而是随着模块数量呈线性增长当数据包处理本身已经很轻量时线程之间传递数据的成本反而会超过真正的业务逻辑。八、一个容易被忽略的问题Memory Barrier除了CAS之外。rte_ring还有另一个隐藏成本。就是Memory Barrier内存屏障。为了保证Producer写入对象以后Consumer一定能够看到。CPU不能随意重排序。因此。每一次enqueue dequeue都会伴随Acquire、Release、Fence。这些操作不会增加CPU利用率。但是会降低流水线并行能力。很多Perf里面Backend Stall实际上就来自这里。未完待续