歡迎光臨
每天分享高質量文章

投機性缺頁異常處理

英文地址:https://lwn.net/Articles/730531/

Speculative page-fault handling(投機性缺頁異常處理)的又一次嘗試

背景

大家都知道避免缺頁異常帶來的效能損耗最好的辦法是避免產生缺頁(我說了一句廢話。。。)。但實際上使用者態程式根本做不到這一點,而對於一個多執行緒程式而言這個問題尤其嚴重,所以核心就需要想盡辦法將缺頁異常處理帶來的額外開銷降到最低。於是乎最近一個八年老坑Speculative page-fault handling終於可能要到被考慮合併進主線的階段了。

Linux核心使用mmapsem來序列化對描述行程地址空間的資料結構的訪問,而缺頁異常的處理也需要訪問mmapsem。所以多執行緒程式在訪問記憶體方面的能力是嚴重受到獲取mmapsem讀/寫訊號量的能力制約的。而由於執行緒數很多所產生的缺頁異常就更加容易發生資源爭用。為瞭解決這個問題,核心研發人員提出了Speculative page-fault handling,它的基本思路是當我們對行程的virtual memory areas (VMA) 進行訪問的時候不持有mmapsem,從而實現無鎖讀訪問。

Speculative page-fault handling的patch,第一次出現是在2009年(八年了,核心研發的效率啊!),之後斷斷續續地,多個核心開發者又對它進行了討論和改進,但相關工作一直沒有被merge進主線。Laurent Dufour最近重啟了這些工作,fix了這些patch的bug,新增上了自己的改進並重新提交到了郵件串列。之後大家就開始在linux-kernel的郵件串列上對這份工作展開了活躍的討論。一個令人激動的事實是,在Dufour的效能測試報告中,當我們啟用了Speculative page-fault handling後,讀取一個2TB的資料庫會得到20%的速度提升。

如上所述,mmapsem是多執行緒程式一個顯著的資源爭用點。而我們關註的缺頁處理也需要訪問行程的VMA結構。VMA結構描述了行程的記憶體佈局,因此要求缺頁處理時需要持有mmapsem。即使只要求讀鎖(我們討論的缺頁處理即為這種情況),我們也會頻繁訪問mmapsem,從而導致快取顛簸(cache-line bouncing)以及效能下降。Speculative page-fault handling背後的思想是,透過避免在缺頁異常處理中使用mmapsem,無鎖遍歷VMA,從而提高記憶體訪問的效能。但是我們不得不說,設計和使用mmap_sem就是為瞭解決一些不好解決的同步問題,現在不持有鎖了,這些問題就得想其他的辦法了。

問題和解法

第一個問題是:假如不持有mmapsem,當我們處理缺頁異常的時候,如果對應的VMA描述中的區域發生了變化怎麼辦?應對這個問題的策略是,盡可能把與VMA狀態無關的工作都先做掉,然後在直接改變行程地址空間之前,再檢查一下VMA是否發生了改變。舉例來說,當我們從磁碟讀資料到記憶體的時候,我們可以先分配一個記憶體頁,將資料讀取出來,這些階段都是不需要mmapsem的,而當我們把這個頁加入到行程地址空間的時候我們需要一個一致的VMA,所以這個時候是需要拿mmap_sem的。

對於這個問題的解決,內核有一直都有一個機制叫seqlock。所以這套patch把seqlock新增到了VMA結構中,在所有改變VMA的地方都遞增sequence count。Speculative fault-handling程式碼便可以在工作之前記錄sequence number,並且在工作結束後檢查sequence number有沒有改變。如果sequence number改變了,我們可以知道VMA也改變了,投機進行的工作就當做白做了。這種情況就是失敗的投機嘗試,此時只能繞過Speculative fault-handling,並按照老辦法重試處理缺頁異常。

第二個問題要更棘手一些,沒有持有mmap_sem,在處理缺頁異常的時候,一個VMA可能會完全消失。這種情況可以使用Read-Copy-Update(RCU)來避免,使用RCU,可以保證在處理缺頁異常的時候,VMA結構是存在的。當然因為缺頁異常處理過程中的很多操作都可能會睡眠,所以我們要使用SRCU(RCU的一種可睡眠的變體)來序列化VMA的更新。

進行Speculative fault-handling時,核心會先無鎖地遍歷頁表,同時持有一個細粒度的頁表鎖。然後呼叫srcureadlock()以便進行VMA查詢。最後檢查VMA的write-sequence count。未來遵守內核的鎖使用順序,我們需要先放棄頁表鎖,然後使用VMA來找到發生故障的地址所在的頁。一旦頁找到了,VMA需要重新驗證一次,進行頁表遍歷、獲取頁表鎖,並檢查VMA的sequence number是否沒有改變。如果沒有改變,那麼頁被安裝到頁表裡,頁表鎖也被釋放。

speculative page-fault handling的另一個難題與Translation lookaside buffer(TLB)的失效有關。很多行為,例如unmapping一個記憶體區域,都會導致TLB的失效。失效TLB的過程是傳送處理期間中斷(IPI)來告訴每個CPU失效它自己的TLB。unmap的呼叫路徑可能會在鎖住特定的頁表項期間進行TLB失效操作。此時,Speculative-fault-handling可能在關中斷的情況下嘗試獲取頁表鎖,如果這種嘗試的頁表項被鎖在unmap的路徑上,處理器會在關中斷的情況下自旋,因此永遠收不到TLB失效的IPI,這將導致死鎖。這是比mmap_sem競爭更壞的情況。瞭解清楚這個問題後,解決辦法也顯而易見:在speculative路徑使用trylock操作獲取鎖,如果獲取鎖失敗,則立即fall back到傳統page fault處理流程上。

歷史

第一套speculative page fault相關的patch由Hiroyuki Kamezawa在2009年釋出,接下來Peter Zijlstra組織了核心社群的討論並開發了他自己的實現,他使用了RCU來完成無鎖讀VMA。不過Peter的實現也有一些問題,所以沒能被合併進主幹。到了2014年,由於很多之前導致他的patch不能工作的問題都已經解決了,所以Peter Zijlstra重啟了這個想法。然而,討論再次無疾而終。今年6月,Dufourt在最新核心移植了PeterZ的patch,同時也添加了自己的一些patch,然後重新傳送到了郵件串列。不過Dufour提到,他的patch集裡仍然存在TLB失效的問題。

拋開前面兩個廢棄掉的Speculative page fault的實現不說,這套patch目前的進展已經非常不錯了,所以也許社群可以認真考慮一下合併進核心主線的事情。Dufour的測試中關於資料庫讀取效能的提高也引起核心其他開發者,如Michal Hocko的註意,Hocko追問Dufour是否在別的benchmark,例如kernbench或其他的高度多執行緒的測試負載上測試過這套patch的收益。作為回應,今年8月8日Dufour給出了很多不同benchmark的測試結果,顯示了針對不同的測試,結果也有差異(有的提升顯著,有的基本沒有提升)。

到此為止,這套patch的主要問題已經都解決了。鑒於speculative page-fault handling對於部分測試負荷有著顯著的效能提升,在這份工作最初的想法浮出水面八年後,我們有理由相信這份工作有望在不遠的未來被合併。希望更多的應用能夠最終收益。

贊(0)

分享創造快樂