为什么我的 DPDK 程序没有丢包,吞吐也很高,但 CPU 却越来越“卡”?一次排查让我彻底理解 memory fragmentation
我在做DPDK开发时经常性的会重点关注PPS吞吐时延丢包率但我曾经遇到过一个非常奇怪的问题程序没有丢包吞吐稳定CPU 利用率也正常但是系统运行几天后性能会越来越差。表现为worker 偶发卡顿burst 波动越来越大latency jitter 增加cache miss 上升CPU pipeline stall 增多最奇怪的是重启程序后一切恢复正常。第一次遇到时我怀疑过NUMAmbuf 泄漏RX queue 抖动ring 堵塞网卡 DMA 问题最后才发现真正的问题竟然是memory fragmentation即内存碎片化。而这个问题也让我真正理解了DPDK 为什么如此强调mempoolhugepagefixed-size objectper-core cache这些设计。一、问题现场程序是一个典型的数据面架构RX core收包。worker core解析协议。TX core转发。系统运行初期非常稳定22 Mpps二、但运行几天后开始出现PPS 波动22M - 18M - 15Mlatency jitterP9980us - 700uscache miss 增加perfLLC miss rapidly increasing三、第一反应是不是内存泄漏因为性能越来越差。很像内存耗尽。于是检查free -h结果内存很正常。四、再看 hugepage查看cat /proc/meminfo | grep Hugehugepage 也没耗尽。五、真正奇怪的地方程序没有malloc/free几乎全部使用DPDK Mempool即固定对象池。理论上不应该碎片化。六、后来终于发现问题业务模块里偷偷用了malloc()用于flow metadatasession infodynamic buffer尤其某些协议字段长度不固定。于是大量小对象频繁申请/释放七、什么是 memory fragmentation即虽然总内存很多。但连续可用空间越来越少。八、举个简单例子例如最开始[AAAAAAAAAAAAAAAAAAAA]后来不断申请/释放[AA__AAA___AA_A____]虽然空闲空间总量很大。但已经不连续。九、为什么碎片化会影响性能很多人以为碎片化只影响“能否分配成功”。其实更严重的是cache locality 下降。十、DPDK 为什么极度依赖 localityDPDK 的高性能核心之一连续内存。例如mbuf poolRX descriptor ringflow table全部强调cache-friendly。十一、碎片化后发生什么原本相邻数据cache line contiguous现在变成scattered everywhere于是prefetch 失效TLB miss 增加cache miss 增加memory access latency 增加十二、为什么运行时间越久越严重因为碎片化是累积问题。短时间看不出来。长期运行越来越严重。十三、为什么重启恢复正常因为进程退出后整个地址空间重新整理。碎片消失。十四、进一步理解 malloc 的问题glibc malloc本质不是为20 Mpps packet processing 设计的。它更偏向通用应用。十五、DPDK 为什么几乎不用 malloc你会发现DPDK 官方代码大量使用mempoolmemzoneringfixed-size object原因就是避免动态碎片化。十六、mempool 的核心思想例如预分配固定大小对象大小一致连续排列cache aligned十七、为什么 fixed-size 非常重要因为固定大小意味着不会出现小洞即不会形成严重碎片。十八、另一个关键问题TLB miss碎片化不仅影响 cache。还影响TLB。十九、什么是 TLBCPU 会缓存虚拟地址 - 物理地址 映射。即Translation Lookaside Buffer。二十、碎片化后内存页分散。TLB 命中率下降。导致page walk 增加。进一步拖慢性能。二十一、perf 数据终于暴露真相后来分析perf top发现大量时间消耗在malloc free memcpy以及TLB miss handling二十二、真正修复方案后来彻底改造1. 所有 flow object 池化不再malloc/free改为object pool2. fixed-size session object统一大小。3. per-core cache避免跨核竞争。4. hugepage alloc关键数据放 hugepage。二十三、优化后效果优化前时间PPS启动时22 Mpps3 天后15 Mpps优化后时间PPS启动时22 Mpps7 天后22 Mpps稳定很多。二十四、为什么高性能程序都喜欢“预分配”现在你会发现很多系统都这样DPDKVPPRedisClickHouse游戏服务器核心思想避免 runtime allocation。二十五、为什么内存管理比算法更重要很多人优化只关注SIMDlocklessbatch但实际上真正限制长期稳定性的经常是memory behavior。二十六、进一步理解 DPDK 哲学DPDK 的设计理念之一不是“动态灵活”。而是predictable performance。即固定大小固定 pipeline固定内存布局换取稳定性能。二十七、工程经验总结高 PPS 程序尽量避免malloc/freevariable-length objectruntime resize动态链表优先mempoolslabobject cacheper-core object二十八、这次排查真正学到什么以前我以为性能优化主要是CPU。后来才意识到真正决定系统稳定性的往往是memory layout。因为现代 CPU真正慢的不是计算。而是memory access。二十九、总结为什么 DPDK 程序没有丢包、吞吐也高但 CPU 越来越“卡”很多时候不是网卡问题算法问题NUMA 问题而是memory fragmentation。通过这次问题我们真正理解了核心概念memory fragmentationmempoolfixed-size objectcache localityTLB misspredictable performance这也是高性能网络开发真正深入之后才会理解的一点决定性能上限的很多时候不是代码逻辑。而是内存行为。