为什么 DPDK VirtIO 只能跑到 40Gbps?—— 一次 KVM 虚拟交换机性能瓶颈定位全过程(下)
接上文:为什么 DPDK VirtIO 只能跑到 40Gbps?—— 一次 KVM 虚拟交换机性能瓶颈定位全过程(上)-CSDN博客
七、Split Virtqueue:为什么一个队列要维护三张环?
定位到 Virtqueue 之后,我们继续深入分析。
很多开发者第一次看到 VirtIO 的数据结构都会疑惑:
为什么普通网卡只有一个 Descriptor Ring,而 VirtIO 却维护三张 Ring?
实际上,在Split Virtqueue中,真正的数据结构包括:
Descriptor Table + Available Ring + Used Ring三者职责完全不同。
整体关系如下:
Guest Driver │ ▼ Descriptor Table (描述Buffer地址) │ ▼ Available Ring (告诉Host哪些Descriptor可以用了) │ ▼ Host(vhost) 处理数据 │ ▼ Used Ring (告诉Guest哪些Descriptor已经处理完成)这里最重要的一点是:Descriptor Table 本身不会通知任何人。
真正负责"发布"消息的是:
avail->idx以及:
used->idx直到这里。
我们才真正理解:
为什么上一篇 Memory Ordering 会如此重要。
VirtIO 的整个同步机制,本质上也是:
先构造对象,再发布 Index。
八、为什么小包性能下降如此明显?
继续分析 Perf。
我们发现:
64Byte包CPU大量时间花在:
virtqueue_notify() ↓ eventfd_write() ↓ KVM ↓ VM Exit为什么?
假设Burst只有:
1 Packet流程:
Guest ↓ Descriptor ↓ avail->idx++ ↓ Kick ↓ VM Exit ↓ Host 处理 ↓ Notify ↓ VM Entry ↓ 继续一次Packet。
一次VM Exit。
一次VM Entry。
CPU大量时间浪费在虚拟化切换。
而1500Byte情况不同。
虽然Packet数量减少。
但是每个Packet携带的数据更多。
最终100Gbps对应PPS大幅下降。
例如:
| 包长 | PPS(约) |
|---|---|
| 64Byte | 148.8 Mpps |
| 512Byte | 24 Mpps |
| 1500Byte | 8.1 Mpps |
也就是说:
VirtIO真正限制的是PPS。
不是Gbps。
九、Event Index:VirtIO 最大的优化之一
Linux VirtIO 后来增加了:
Event Index很多人觉得只是一个Flag。
实际上:
它改变了整个Notification模型。
传统模式:
每次 avail++ ↓ KickHost不停收到Notify。
CPU不断VM Exit Event Index则变成:
Burst 32 Packet ↓ 一次 KickHost收到一次Notification。
继续处理32 个Descriptor。
CPU VM Exit次数下降几十倍。
很多云厂商VirtIO性能能够突破80Gbps。
最重要原因之一就是Event Index。
十、Packed Virtqueue 为什么越来越流行?
虽然Split Virtqueue已经使用很多年。
但是:
越来越多的新平台开始默认Packed Virtqueue。
原因只有两个字:
Cache。
Split模式需要CPU不停访问:
Descriptor ↓ Available Ring ↓ Used Ring三个不同区域。
CPUCache不断Miss。
Packed则把Descriptor状态直接放在同一结构。
例如:
Descriptor Flags Wrap CounterCPU几乎连续访问内存。
Cache命中率显著提高。
不仅减少Cache Miss。
同时减少Memory Barrier。
因此新版本VirtIO几乎全部推荐Packed。
十一、DPDK vhost-user 为什么还能继续优化?
很多人认为开启vhost-user性能已经最佳。
实际上DPDK还做了很多优化。
例如:
批量处理Descriptor。
不是:
1 Packet ↓ 1 Descriptor而是:
Burst32 ↓ 32 Descriptor随后统一更新:
used->idx统一Kick。
这样:
Notification次数再次下降。
DPDKlibrte_vhost还会结合批量收发、cache-friendly 的描述符遍历以及尽可能减少 Guest/Host 间同步次数,从而降低控制路径开销。
十二、问题最终是如何解决的?
最终整个系统进行了如下优化:
①开启:Packed Virtqueue。
②开启:Event Index。
③Burst由:
16 ↓ 32④Guest:开启Multi Queue。
⑤Host:每个Queue绑定独立PMD。
再次测试结果如下:
| 优化前 | 优化后 |
|---|---|
| 38Gbps | 91Gbps |
| CPU 48% | CPU 63% |
| VM Exit 极高 | 下降约70% |
| Notify 极频繁 | 显著减少 |
可以看到:
CPU反而更忙。
但是:
吞吐提升超过2 倍。
真正说明:
以前CPU不是处理Packet。
而是浪费在Notification。
十三、生产环境有哪些经验?
经过这次故障。
团队总结了几条经验。
① 不要只盯着 PMD
VirtIO瓶颈很多发生在控制路径。
不是DMA。
不是Descriptor。
而是Notification。
② PPS 比 Gbps 更重要
很多100G测试都是1500Byte。
实际上真正决定VirtIO性能的是PPS。
尤其64Byte。
③ Event Index 建议开启
如果业务允许批量发送。
Event Index收益非常明显。
不过也要注意,批量通知会增加一定的数据包完成延迟,因此需要根据业务对时延和吞吐的要求进行权衡。
④ 优先使用 Packed Virtqueue
对于支持 VirtIO 1.1 及以上的平台,Packed Virtqueue 通常能带来更好的缓存局部性和更低的同步开销,是值得优先考虑的选择。
⑤ Host 和 Guest 要共同优化
VirtIO不是Host一个人的事情。
Guest、Burst、Queue、Affinity。
都会影响最终性能。
总结
回到文章开头的问题:
为什么 DPDK VirtIO 只能跑到 40Gbps?
很多团队最初都会怀疑:
- PMD 不够快;
- RSS 不均衡;
- Descriptor 太少;
- HugePage 配置错误;
- CPU 性能不足。
但真正的问题往往隐藏在更深的地方。
在 VirtIO 架构中,数据真正走的是共享内存,而性能瓶颈常常来自控制路径——Descriptor 发布、Kick、Notify、Event Index、VM Exit 等同步机制。
尤其是在 64Byte 小包场景下,PPS 极高,每一次 Notification 的成本都会被无限放大。当通知过于频繁时,CPU 花费的大量时间并不是转发数据,而是在 Guest、KVM、vhost-user 和 Host 之间不断切换。
因此,一个高性能的 VirtIO 系统,不只是需要优秀的 DPDK 数据平面,更需要合理设计通知机制、队列模型和批处理策略。
真正理解 Virtqueue 的工作原理之后,再去分析 vhost-user、Packed Virtqueue、vDPA,甚至硬件 VirtIO 加速,就会发现它们都在围绕同一个目标进行优化:
减少同步,减少通知,减少切换,让 CPU 更多时间用于真正的数据转发,而不是控制路径。
