本文描述了“vHost User NUMA感知”的概念,該特性的測試表現,以及該特性為ovs+dpdk帶來的性能提升。本文的目標受眾是那些希望了解ovs+dpdk底層細節的人,如果你正在使用ovs+dpdk在NUMA host上配置虛擬化平台,使用vHost User類型的port作為guest的虛擬網絡配置,那么本文或許會給你一些優化性能的靈感。
vHost User NUMA感知
vHost User NUMA感知特性在DPDK 2.2版本時引入,該特性的引入是為了解決DPDK中的一個拖后腿的地方:在多NUMA節點的環境中使用DPDK,vHost的內存分配效率比較低。為了了解這個拖后腿的點,我們先必須了解vHost User設備使用的三種內存:
- 由DPDK分配管理的內存,Device tracking memory
- 由OVS分配管理的內存,mbufs
- 由QEMU管理分配的內存,Guest memory(device and memory buffers)
在多NUMA節點環境中,顯然如果要優化性能,這三種內存應當分配在同一個NUMA節點上。但就這個小小的要求,在DPDK 2.2版本之前都是不可能達到的,因為在2.2版本之前,所有由DPDK分配管理的Device tracking memory內存,都來自同一個NUMA節點,即便使用這些內存的vHost User設備被其它NUMA節點上跑着的虛擬機使用着。這就會有一種尷尬的場景出現:一台虛擬機,QEMU為它分配的guest memory在節點A上,而DPDK的device tracking memory在另外一個節點B上。這種尷尬的場景會直接導致Intel QuickPath Interconnect(QPI)堵車,顯然也會有其它方面潛在的性能損耗。這個場景的示意圖如下:

在DPDK 2.2版本之后,DPDK中的vHost結構被優化成了動態的與QEMU管理的guest memory貼在一起。這時,當一個vHost設備出生的時候,DPDK為它分配的內存不再固定,變得有點像一個臨時內存區,這個vHost設備將在這個臨時內存區開心的活着,直到QEMU通知DPDK:“嘿,小同志,我需要一個vHost設備”。當QEMU向DPDK索取一個vHost設備的時候,顯然QEMU需要向DPDK發送消息,而DPDK就可以利用這個消息去確定這個索要vHost設備的虛擬機位於哪個NUMA節點,之后,這個vHost設備的內存也將遷移至這個NUMA節點上。
換句話說,vHost設備出生時居住在一個臨時住所,直至QEMU前來領養它,之后它才有一個穩定的家。
現在我們解決了2/3的問題,還有一部分內存上文沒有提到,那就是由OVS分配管理的mbufs。這些內存由OVS分配管理,旨在提高datapath的運行效率,為了優化性能,顯然它們也應當與QEMU及DPDK管理分配的內存位於內一個NUMA節點上。目前,這個功能由DPDK向OVS發送消息實現,DPDK會向OVS發送有關虛擬機依存的NUMA節點信息的消息,之后OVS將把mbufs使用的內存分配在正確的NUMA節點上。在DPDK向OVS發送這些消息之前,mbufs的內存始終分配在DPDK master lcore所在的NUMA節點上。
現在三部分內存都位於同一個NUMA節點了,還剩下最后一個問題:PMD輪詢線程(poll mode driver threads)。
PMD輪詢線程是一些比較苦逼的線程,它們日夜不停馬不停蹄的輪詢input ports,對收到的包進行分類,並對包執行相應的actions。在“vHost User NUMA感知”特性出現之前,所有OVS中的PMD輪詢線程都住在同一個NUMA節點上,即是DPDK的master lcore所在的NUMA節點。終於,現在,社會解放了,好日子來了,PMD輪詢線程和mbufs、guest memory、device tracking memory呆在同一個NUMA節點了。
下圖展示了三塊內存及PMD輪詢線程位於同一個NUMA節點時的場景:

性能測試環境
測試環境需要一個至少有兩個NUMA節點的host。上面跑着ovs+dpdk,ovs-bridge上有兩個vHost User設備,我們稱之分別為vhost0與vhost1。兩個虛擬機跑在不同的NUMA節點上,分別稱之為vm0與vm1。vhost0與vm0是一對,vhost1與vm1是一對。
下面是測試環境的規格:
Processor E5-2695 v3
Kernel 4.2.8-200
OS Fedora* 22
QEMU* 2.6.0
DPDK 16.04
OVS 914403294be2
測試環境配置過程
在安裝DPDK與OVS之前,確保NUMA庫已安裝
sudo yum install numactl-libs sudo yum install numactl-devel
確保編譯DPDK時打開了以下的配置項
CONFIG_RTE_LIBRTE_VHOST_NUMA=y
編譯DPDK
鏈接DPDK庫,編譯OVS
這些都沒啥可說的,畢竟裝了幾百回了,閉着眼睛也會做了。
配置ovs-bridge,就像上面說的那樣:創建一個ovs-bridge,在下面創建兩個ovs-port,類型為dpdkvhostuser或dpdkvhostuserclient。設置ovs的other_config:pmd-cpu-mask掩碼時,為兩個NUMA節點雨露均沾,平均分配。比如,在一個28個邏輯核心的機器上,0~13號核心在NUMA節點0上,14~17號核心在NUMA節點1上,那么如下設置就是雨露均沾:
ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask=10001
# 10001是16進制,翻譯成二進制是10000000000000001,即PMD輪詢線程的核心親合設置為0號核心與16號核心兩個核心,其中0號核心位於NUMA節點0,1號核心位於NUMA節點1 # 這篇文章比較奇怪,只有一個cpu socket,這個cpu型號,即E5 2695 v3是14核心28線程的,按理來說應該只有一個numa節點啊。難道用一個cpu socket也能組兩個numa節點?
在啟動虛擬機之前,使用如下命令檢查一個pmd設置
ovs-appctl dpif-netdev/pmd-rxq-show
在啟動虛擬機之前,QEMU還沒有分配內存,也肯定談不上發消息給DPDK,DPDK就更談不上發消息給OVS了,所以此時這時PMD線程將落在同一個NUMA節點上,顯示如下:
pmd thread numa_id 0 core_id 0: port: dpdkvhostuser1 queue-id: 0 port: dpdkvhostuser0 queue-id: 0
然后啟動虛擬機,在兩個NUMA節點上分別啟動vm0與vm1,下面以qemu為例,為了確保兩個虛擬機分別跑在兩個NUMA節點上,使用taskset命令,如下:
sudo taskset 0x2 qemu-system-x86_64 -name vm0 -cpu ... sudo taskset 0x2000 qemu-system-x86_64 -name vm1 -cpu ...
這時查看虛擬機的log,vm1會打印出如下的log:
VHOST_CONFIG: read message VHOST_USER_SET_VRING_ADDR VHOST_CONFIG: reallocate vq from 0 to 1 node VHOST_CONFIG: reallocate dev from 0 to 1 node
出現上面這樣的log就意味着DPDK的device tracking memory被從臨時住所挪到了正確的NUMA節點上
另外一個驗證方法是使用pmd-rxq-show工具,顯示如下:
pmd thread numa_id 1 core_id 20: port: dpdkvhostuser1 queue-id: 0 pmd thread numa_id 0 core_id 0: port: dpdkvhostuser0 queue-id: 0
dpdkvhostuser1現在被一個位於NUMA節點1上的線程服務着,這也正是vm1所在的NUMA節點