GPU虛擬機器建立時間深度優化

貧老農 發表於 2020-09-23
GPU

從公有云服務商那裡購買過虛擬主機的資深使用者,一般會發現這麼個規律:建立一臺CPU虛擬主機是比較快的,但是要建立一臺包含GPU卡的虛擬主機通常需要等比較長的時間,整個建立過程短則數十秒鐘,長則數分鐘。對於絕大多少的使用者來說,虛擬主機的建立時間長一點對他們影響並不大,因為建立虛擬機器屬於相對低頻操作。但是也會有一些特定的使用者由於其業務場景互動性比較強,會對虛擬主機的建立時間有相對苛刻的要求,因為過長的建立時間會導致其業務使用者體驗很差。本文將從虛擬化的角度來介紹GPU虛擬主機建立時間長背後的原因,以及相關的優化方法。

通過分析Libvirt, QEMU以及Guest 內的相關日誌及對應的時間戳,可以獲取GPU虛擬主機在建立過程中的耗時情況,這裡我們主要關心幾個關鍵的時間點:  a) Libvirt 開始建立QEMU 程式;b) Libvirt 執行 Resume啟動VCPU ;  c) Guest kernel 列印第一條日誌.  在本文中,我們把a和 b 之間的時間間隔稱為QEMU初始化時間, 把b 和c 之間的時間間隔稱為 BIOS執行時間。以下資料是在滴滴雲的線上環境中採集到的建立一臺包含8個CPU核虛擬機器例項的相關資料:

GPU虛擬機器建立時間深度優化

從上面的資料可以看到,對於規格相同的虛擬機器例項,帶1塊P40卡的GPU例項相比同規格的CPU例項在QEMU初始化及BIOS執行部分的時間都明顯要長, 在帶4塊P40卡以及更大記憶體規格的場景下,需要的時間會進一步拉長。通過實驗我們發現在主機配置和GPU卡型號確定的前提下,GPU例項的建立時間長短主要取決於兩個因素:虛擬機器的記憶體大小和GPU卡的數量。

為什麼GPU例項的建立過程要比CPU例項的建立過程耗時長?多消耗的時間到底花在哪裡?要搞清楚原因需要深入的分析,比較直觀的辦法就是通過perf取樣來生成火焰圖,以此來分析虛擬機器在建立過程中的熱點函式。下圖是在滴滴雲環境裡抓取到的GPU虛擬機器啟動過程中QEMU程式的火焰圖。

GPU虛擬機器建立時間深度優化

通過對程式碼呼叫關係的分析,可以得知熱點發生在系統分配記憶體和對記憶體頁面清零的過程中,是由QEMU中的vfio_dma_map函式在執行VFIO_IOMMU_MAP_DMA  ioctl 系統呼叫所觸發,該呼叫會Pin住所有分配給VM當做RAM使用的記憶體。在Pin 記憶體的過程中,如果虛擬記憶體對應的物理頁面尚未分配,會先進行實體記憶體分配並對記憶體頁面內容進行清零。在Linux kernel 中,對分配給應用程式的記憶體進行清零主要是基於安全方面的考慮,避免Host 記憶體中的內容洩漏給使用者空間的應用程式。這裡之所以要將記憶體Pin 住,目的是為了保證IOMMU IO頁表和 host HVA->HPA 對映的一致性,否則Guest 內裝置的DMA操作可能會訪問到錯誤的記憶體頁面。

VFIO DMA 對映處理慢可以在一定程度上解釋為什麼記憶體的大小和GPU卡的數量會影響到GPU例項的建立時間。虛擬機器例項記憶體規格越大,需要對映和Pin住的記憶體量也就越大,相關處理的耗時和記憶體量成正比。另外GPU卡上通常會包含一塊比較大的MMIO區域,對MMIO的對映也會耗費較多的時間,卡的數量越多,耗時就會越長。相比之下,CPU例項的建立過程沒有VFIO DMA 對映的相關處理流程,因此會比較快。

針對以上的熱點,有什麼辦法可以消除或者緩解呢?已經有業內的同行們提到過這個問題並給出了對應的解決方案,其思路是對分配給VM 用作RAM使用的記憶體區域做一個標記,在核心中跳過對標記的記憶體頁面進行清零,而將清零的動作留給QEMU來做,在QEMU 中可以利用多執行緒以及更高效的指令進行清零動作,從而加速Pin記憶體的過程。該方案的缺陷主要有兩點:  一是存在安全性風險,其他應用程式可以利用設定的標記來窺探host 記憶體中的資訊;二是在VM例項的VCPU個數比較少的情況下,優化效果不是很好。

我們採用了另外一種方案,通過修改Host kernel的記憶體管理部分, 我們實現了一種對Host 上空閒實體記憶體提前進行清零的機制,清零動作可以在系統空閒的時候進行,當某個記憶體頁面被清零後,將其對應的 struct page 進行標記,這樣在需要對記憶體進行清零的時候,可以通過檢查該標記來判斷是否要執行清零動作,如果清零的標記已經被設定,就可以跳過清零的步驟。該方案避免了上述方案中的兩個主要問題,同時還有其它方面的好處,主要包括以下幾點:a.可以提高缺頁異常處理效率,尤其是透明大頁的缺頁異常處理效率;b. 可以加速需要Pin記憶體及需要通過mlock 來鎖住記憶體的應用場景,例如使用RDMA, QAT 硬體加速等場合;c.  可以加速核心中其他需要對記憶體進行清零的場景。相關補丁的RFC版本,我們已經提交到了Linux kernel 社群。

另一個加速Pin記憶體的有效方法是採用大頁,通過開啟透明大頁可以顯著減少缺頁處理的呼叫次數並加速Pin記憶體的過程。下圖展示了開啟透明大頁以及啟用空閒記憶體預清零機制對GPU例項創建立時間的影響。

GPU虛擬機器建立時間深度優化

以上的資料表明,在開啟透明大頁以及空閒記憶體預清零功能後,可以顯著的的優化QEMU的初始化時間,但是BIOS部分的耗時依然偏長。通過進一步的分析我們發現主要的時間消耗還是在VFIO 對映DMA的處理過程當中,主要有幾個方面的原因:a. 對映DMA Pin記憶體需要逐頁查詢頁表,開銷較大;b. QEMU 存在對部分IOVA區域的反覆對映及解除對映的操作。於是我們嘗試在這兩個方向上進行優化,通過採用批量處理的方法減少查詢頁表的開銷,另外在QEMU中加入VFIO DMA對映區域的管理,有效的規避了效率低下的反覆對映及解除對映操作,最終大幅度降低了VFIO DMA對映的時間消耗。

在解決完上述問題後我們並沒有止步,對虛擬機器例項建立過程中的可優化的其它地方,我們也做了相關的處理,例如關閉BIOS boot menu ,優化VFIO PCI 裝置reset 的流程,去掉對GPU例項來說不必要的操作,最終將GPU例項建立過程中虛擬化部分的時間開銷減少了90%以上,下面這張圖展示了單卡小記憶體規格例項優化前後的耗時對比:

GPU虛擬機器建立時間深度優化

大記憶體規格和多GPU卡的效果更加顯著,時間減少了95%以上,相關資料如下圖:

GPU虛擬機器建立時間深度優化

經過上述的優化,目前在滴滴雲上建立一個GPU例項的速度比優化前顯著加快,甚至比優化前建立一個CPU例項的速度還要快,如果使用者對GPU例項的建立速度有比較強的需求,歡迎到滴滴雲上進行體驗