22-系統案例:如何提高iTLB(指令地址映射)的命中率


我們今天繼續探討性能優化的實踐,介紹一個系統方面的優化案例。這個案例涉及好幾個方面,包括CPU的使用效率、地址映射、運維部署等。

開發項目時,當程序開發完成后,生成的二進制程序需要部署到服務器上並運行。運行這個程序時,我們會不斷衡量各種性能指標。而生產實踐中,我們經常發現一個問題:是指令地址映射的不命中率太高(High iTLB miss rate),導致程序運行不夠快。我們今天就探討這個問題。

在我過去的生產實踐中,針對這一問題,曾經采取的一個行之有效的解決方案,就是同時進行二進制程序的編譯優化采用大頁面的部署優化。我下面就詳細地分享這兩個優化策略,並介紹如何在公司生產環境中,把這兩個策略進行無縫整合。

為什么要關注指令地址映射的不命中率?

我們先來看看為什么需要關注iTLB的命中率

在以往從事的性能工作實踐中,我觀察到CPU資源是最常見的性能瓶頸之一,因此提高CPU性能,一直是許多性能工作的重點。

導致CPU性能不高的原因有很多,其中有一種原因就是較高的iTLB不命中率。這里的iTLB就是Instruction Translation Lookaside Buffer,也就是指令轉換后備緩沖區。iTLB命中率不高,就會導致CPU無法高效運行。

那么TLB(轉換后備緩沖區)又起到了什么作用呢?

我們知道,在虛擬內存管理中,內核需要維護一個地址映射表,將虛擬內存地址映射到實際的物理地址,對於每個內存里的頁面操作,內核都需要加載相關的地址映射。在x86計算體系結構中,是用內存的頁表(page table),來存儲虛擬內存和物理內存之間的內存映射的。但是,內存頁表的訪問,相對於CPU的運算速度,那是遠遠不夠快的。

所以,為了能進行快速的虛擬到物理地址轉換,TLB(轉換后備緩沖區)這種專門的硬件就被發明出來了,它可以作為內存頁表的緩存。TLB有兩種:數據TLB(Data)和指令TLB(Instruction),也就是iTLB和dTLB;因為處理器的大小限制,這兩者的大小,也就是條目數,都不是很大,只能存儲為數不多的地址條目。

為什么說TLB的命中率很重要呢?

這是因為,內存頁表的訪問延遲比TLB高得多;因此命中TLB的地址轉換,比未命中TLB也就快得多。因為CPU無時無刻不在執行指令,所以iTLB的性能尤其關鍵。iTLB未命中率,是衡量因iTLB未命中而導致的性能損失的度量標准。當iTLB未命中率很高時,CPU將花費大量周期來處理未命中,這就導致指令執行速度變慢。

具體來講,iTLB命中和不命中之間的訪問延遲,差異可能是10到100倍。命中的話,僅需要1個時鍾周期,而不命中,就需要10-100個時鍾周期,因此iTLB不命中的代價是極高的。

我們可以用具體的數據來感受一下。假設這兩種情況分別需要1和60個時鍾周期,未命中率為1%,將導致平均訪問延遲為1.59個周期,相比全部命中的情況(即1個周期)的訪問延遲,足足高出59%。

如何提高指令地址映射的命中率?

對於iTLB命中率不高的系統,如果能提高命中率,可以大大提高CPU性能並加快服務運行時間。我們在生產過程中實踐過兩種方案,下面分別介紹。

第一種方案,是優化軟件的二進制文件來減少iTLB不命中率。

一般而言,根據編譯源代碼的不同階段(即編譯、鏈接、鏈接后等階段),分別存在三種優化方法。這樣的例子包括優化編譯器選項,來對函數進行重新排序,以便將經常調用的所謂“熱函數”放置在一起,或者使用FDO(Feedback-Directed Optimization,就是基於反饋的優化)來減少代碼區域的大小。

FDO是什么呢?簡單來說,就是把一個程序放在生產環境中運行,剖析真實的生產數據,並且用這些信息來對這個程序進行精准地優化。比如,可以確切地知道在生產環境中,每個函數的調用頻率。

那么要如何進行二進制的優化呢?

我們可以通過編譯優化來將頻繁被訪問的指令匯總到一起,放在二進制文件中的同一個地方,以提高空間局部性,這樣就可以提高iTLB命中。這塊放置頻繁訪問指令的區域,就叫熱區域(Hot Text)。

在熱區域的指令,它們的提取和預取會更快地完成。還記得我們在第4講學過的帕累托法則嗎?根據對許多服務的研究,帕累托法則在這里依然適用。

通常情況下,有超過80%的代碼是“冷的指令”,其余的是“熱指令”。通過將熱指令與冷指令分開,昂貴的微體系結構資源(比如iTLB和緩存)就可以更有效地處理二進制文件的熱區域,從而提高系統性能。

具體的二進制優化過程,包括以下三個大體步驟:

首先,是通過分析正在運行的二進制文件來識別熱指令。我們可以用Linux的perf工具來達成此目的。你有兩種方法來進行識別:可以使用堆棧跟蹤,也可以使用LBR(Last Branch Record,最后分支記錄)。LBR比較適宜,是因為它的好處是能提高數據質量,並減少數據占用量。

其次,根據函數的訪問頻率,對配置文件函數進行排序。我們可以使用名為HFSort的工具,來為熱函數創建優化表單。

最后,鏈接器腳本將根據訪問順序,優化二進制文件中的函數布局。

這些步驟執行完畢后的結果就是一個優化的二進制文件。我說明一下,如果這里面提到的工具你沒有用過,也沒有關系。這里知道大體原理就行了,當你真正用到的時候,可以再仔細去研究。

第二種方案就是采用大頁面。什么是大頁面呢?

現代計算機系統,除了傳統的4KB頁面大小之外,通常還支持更大的頁面大小,比如x86_64上分別為2MB和1GB。這兩種頁面都稱為大頁面。使用較大的頁面好處是,減少了覆蓋二進制文件的工作集所需的TLB條目數,從而用較少的頁面表就可以覆蓋所有用到的地址,也就相應地降低了采用頁面表地址轉換的成本。

在Linux上,有兩種獲取大頁面的方法:

  1. 手工:預先為應用程序預留大頁面;
  2. 自動:使用透明大頁面,也就是THP(Transparent Huge Pages)。

THP,就像名字一樣,是由操作系統來自動管理大頁面,不需要用戶去預留大頁面。THP的顯著優點是不需要對應用程序做任何更改;但是也有缺點,就是不能保證大頁面的可用性。預留大頁面的方式,則需要在啟動內核時應用配置。假如我們想保留64個大頁面,每個2MB,就用下面的配置。

hugepagesz = 2MB, hugepages = 64

我們在服務器上運行程序時,需要將相應的二進制文件加載到內存中。二進制文件由一組函數指令組成,它們共同位於二進制文件的文本段中,每個頁面都嘗試占用一個iTLB條目來進行虛擬到物理頁面的轉換。

如果內存頁(比如4KB)很小,那么對於一定大小的程序,需要加載的內存頁就會較多,內核會加載更多的映射表條目,而這會降低性能。通常在執行過程中,我們使用4KB的普通頁面。如果使用“大內存頁”,頁面變大了(比如2MB是4KB的512倍),自然所需要的頁數就變少了,也就大大減少了由內核加載的映射表的數量。這樣就提高了內核級別的性能,最終提升了應用程序的性能。這就是大頁面為什么會被引入的原因。

由於服務器通常只有數量有限的iTLB條目,如果文本段太大,大於iTLB條目可以覆蓋的范圍,則會發生iTLB不命中。

例如,Intel HasWell架構中4KB頁面有128個條目,每個條目覆蓋4KB,總共只能覆蓋512KB大小的文本段。如果應用程序大於512KB,就會有iTLB不命中,從而需要去訪問內存的地址映射表,這就比較慢了。iTLB未命中的處理是計入CPU使用時間的,所以等待訪問內存地址映射的過程,就實際上浪費了CPU時間。

我們提出的第二個方案,就是使用大頁面來裝載程序的熱文本區域。通過在大頁面上放置熱文本,可以進一步提升iTLB命中率。使用大頁面iTLB條目時,單個TLB條目覆蓋的代碼是標准4K頁面的512倍。

更重要的是,當代的CPU體系結構,通常為大頁面提供一些單獨的TLB條目,如果我們不使用大頁面,這些條目將處於空閑狀態。所以,通過使用大頁面,也可以充分利用那些TLB條目。

如何獲得最佳優化結果?

我們總共提出了兩個方案,就是采用熱文本采用大頁面放置。這兩個其實是互補的優化方案,它們可以獨立工作,也可以整合起來一起作用,這樣可以獲得最佳的優化結果。

采用熱文本和大頁面放置的傳統方法需要多個步驟,比如在鏈接階段,將源代碼和配置文件數據混合在一起,並進行各種手動配置和刷新,這就導致整個過程非常復雜。這樣的整合方案也就很難廣泛應用到所有的系統中。

我們在生產中構建了一個流程,來自動化整個過程。這樣,該解決方案就成為能被幾乎所有服務簡單采用的方案,而且幾乎是免維護的解決方案。這個解決方案的流程圖如下,整個系統包含三大模塊:程序剖析(Profiling)、編譯鏈接(Linking)和加載部署(loading)。

程序剖析

剖析模塊顯示在圖的頂部。這個模塊定期,比如每周執行一次數據收集作業,以剖析測量正在運行的服務。

Dataswarm是我們曾經采用的數據收集框架,這是Facebook自己開發和使用的數據存儲和處理的解決方案。這個作業剖析了服務的運行信息(例如,熱函數),並且對配置文件進行控制以使其開銷很小。最后,它會把分析好的數據發送到名為Everstore的永久存儲,其實這是一個基於磁盤的存儲服務。

編譯鏈接

在構建服務包時,鏈接程序腳本會從Everstore檢索已配置的熱函數,並根據配置文件,對二進制文件中的功能進行重新排序。這個模塊的運行結果就是已經優化的二進制程序。

加載部署

加載服務二進制文件時,操作系統會盡最大努力,在大頁面上放置熱文本。如果沒有可用的大頁面,則放在常規內存頁面上。

生產環境的性能提升

這樣的解決方案實際效果如何呢?

我們曾經在Facebook的生產環境中廣泛地采用這一優化策略。通過幾十個互聯網服務觀察和測量,我們發現,應用程序和服務器系統的性能都得到了不錯的提升,應用程序的吞吐量差不多提高了15%,服務等待時間減少了20%。

我們也觀察了系統級別的指標。系統級別的指標,我們一般考慮主機cpu使用情況和iTLB不命中率。 iTLB的不命中率幾乎降低了一半,CPU使用率降低了5%到10%。我們還估計,在這里面約有一半的CPU使用率降低是來自熱文本,另一半來自大頁面。

為了幫你更好的認識和體會性能的提升,我下面展示一個具體的互聯網服務,在采用這個解決方案后的性能對比。

這張圖顯示了iTLB不命中率的變化。

在應用該解決方案之前,iTLB不命中率在峰值期間高達每百萬條指令800個,如藍色線表示。采用我們的解決方案優化部署后,iTLB不命中率幾乎下降了一半。具體而言,在峰值期間,最高的iTLB不命中率降低為每百萬條指令425次,如黃線表示,相對優化以前下降了49%,差不多是一半。

應用程序級別指標,顯示在下圖中。

藍色曲線為優化前,黃色曲線為優化后。我們可以看到,應用程序請求查詢延遲的中位數(P50)下降最多25%,P90百分位數下降最多10%,P99下降最多60%。

應用程序吞吐量(QPS)如下圖所示,優化后增長了15%,也就是說,峰值吞吐量從3.6萬QPS增加到了4.1萬QPS。你可以看到,應用程序級別的吞吐量和訪問延遲都得到了改善。

總結

這一講我們討論了如何有效地降低指令地址映射的不命中率太高的問題。

完整的解決方案包括兩部分:編譯優化部署優化。對編譯的優化,是進行指令級別的划分,把經常訪問的指令放在一起,形成Hot Text區域。對程序部署的優化,是采用大頁面。這兩個部分在真正的生產環境中可以一起使用。

唐代的詩人張籍曾經鼓勵一個出身寒門的朋友說:“越女新妝出鏡心,自知明艷更沉吟。齊紈未足時人貴,一曲菱歌敵萬金”。最后兩句的意思是,大家追求的暢銷東西,比如齊國的珍貴絲綢,恰如社會上橫流的物欲,雖然貴重,但是見得多了也就不足為奇了。倒是平時不流行的東西,比如一首好聽的采菱歌曲,更值得人稱道看重。

操作系統的內存頁面管理也有類似的道理,雖然普通的4KB頁面容易管理,幾乎每個程序都在用,大家已經習以為常;但是在某些部署場景下,大頁面的使用會讓系統性能大增,頗有點驚艷的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM