一、一次看起来"毫无异常"的性能下降
某数据中心核心交换机采用DPDK构建高速转发平面。
每个数据包进入Worker以后,需要完成:
- VLAN解析
- L2/L3查找
- Session查找
- ACL匹配
- Forward Action
其中:
Session采用DPDKrte_hash管理。
系统上线半年一直稳定运行。
某次版本升级以后,测试部门反馈:
百万连接场景下:整体吞吐下降约20%。
P99时延:由5.8μs上升至7.9μs。
而:64字节包下降最明显。
查看所有监控。
全部正常。
| 指标 | 状态 |
|---|---|
| PMD CPU | 100% |
| RSS | 正常 |
| RX Queue | 正常 |
| TX Queue | 正常 |
| NIC Error | 0 |
| Hash Miss | 0 |
尤其:
Hash Miss 始终为0。
说明:
所有Session全部命中。
理论上:
Hash应该不存在任何问题。
核心知识点一
很多开发者容易陷入一个误区:
Hash命中率高,就意味着Hash性能高。
实际上。
命中:只是说明找到了对象。
真正决定性能的是:找到对象以后,CPU花了多少Cycle才能真正访问到它。
二、第一轮排查:是不是Hash算法变慢了?
首先怀疑:Hash函数。
查看:DPDK源码。
发现:系统仍然采用:
rte_hash_lookup_data()Hash算法没有修改。
继续统计:平均Lookup次数。
结果:完全一致。
也就是说:每个Packet执行同样次数Hash查找。
理论上:CPU消耗应该一致。
然而:实际Cycles明显增加。
问题开始变得奇怪。
三、Perf开始暴露异常
开始使用:
perf stat \ -e cycles,\ instructions,\ cache-misses,\ branches \ -p <PMD_PID>得到:
正常版本:
| 指标 | 数值 |
| IPC | 1.94 |
| Cache Miss | 很低 |
| Cycles | 正常 |
升级以后:
| 指标 | 数值 |
| IPC | 1.58 |
| Cache Miss | 略增加 |
| Cycles | ↑↑ |
Instructions:几乎一致。
说明:代码没有执行更多。
CPU:只是执行得更慢。
核心知识点二
CPU性能下降:
并不一定意味着执行了更多代码。
更多时候:意味着CPU在等待。
现代CPU真正昂贵的。
不是算术运算。
而是等待内存。
四、热点终于出现
继续:
打开Perf Report。
结果:
热点如下:
35% rte_hash_lookup_with_hash() ↓ memcmp() ↓ session pointer看到这里。
团队第一反应是不是:Hash算法退化了?
继续分析。
却发现:
Hash冲突没有增加。
Bucket长度完全正常。
Hash本身几乎没有变化。
那么:
CPU到底在等待什么?
五、重新认识一次Hash查找
很多开发者理解的Hash:
都是:
Key ↓ Hash ↓ Bucket ↓ Value实际上。
DPDK真正的数据路径远没有这么简单。
一次Session查找,CPU真正经历的是:
Packet ↓ 五元组 ↓ Hash计算 ↓ Bucket ↓ Signature比较 ↓ Key比较 ↓ Session Pointer ↓ Session ↓ Forward Rule ↓ Action注意:
这里已经出现多个不同对象。
这些对象并不一定连续存放。
核心知识点三
Hash查找真正昂贵的。
通常不是Hash函数。
而是:Hash之后的数据访问路径。
如果每一步都发生:Cache Miss。
那么Hash即使100%命中。
整体依然可能很慢。
六、真正的问题开始浮现
继续:
打开PMU事件统计:
perf stat \ -e LLC-load-misses,\ L1-dcache-load-misses,\ stalled-cycles-backend结果:
发现:
升级以后Backend Stall明显增加。
而Hash计算耗时几乎没有变化。
CPU大量时间停留Backend。
说明:
不是ALU。
不是Branch。
而是:
等待数据。
七、为什么CPU一直在等待?
继续:
查看Session结构。
发现:
新版本为了方便扩展。
增加了多个指针成员。
例如:
struct session { struct qos_profile *qos; struct policer *policer; struct stats *stats; struct action *action; };逻辑完全正确。
代码更加模块化。
但是:
每增加一个指针。
CPU就可能多进行一次随机内存访问。
这些访问:
如果:没有命中Cache。
CPU只能等待数据从更低层缓存甚至主存返回。
此时,真正的问题开始指向:Cache Locality(缓存局部性)。