大页内存(HugePages)
原文轉(zhuǎn)載自:http://blog.csdn.net/yutianzuijin/article/details/41912871
今天給大家介紹一種比較新奇的程序性能優(yōu)化方法—大頁內(nèi)存(HugePages),簡單來說就是通過增大操作系統(tǒng)頁的大小來減小頁表,從而避免快表 缺失。這方面的資料比較貧乏,而且網(wǎng)上絕大多數(shù)資料都是介紹它在Oracle數(shù)據(jù)庫中的應(yīng)用,這會讓人產(chǎn)生一種錯覺:這種技術(shù)只能在Oracle數(shù)據(jù)庫中 應(yīng)用。但其實,大頁內(nèi)存可以算是一種非常通用的優(yōu)化技術(shù),應(yīng)用范圍很廣,針對不同的應(yīng)用程序,最多可能會帶來50%的性能提升,優(yōu)化效果還是非常明顯的。 在本博客中,將通過一個具體的例子來介紹大頁內(nèi)存的使用方法。
?????? 在介紹之前需要強調(diào)一點,大頁內(nèi)存也有適用范圍,程序耗費內(nèi)存很小或者程序的訪存局部性很好,大頁內(nèi)存很難獲得性能提升。所以,如果你面臨的程序優(yōu)化問題有上述兩個特點,請不要考慮大頁內(nèi)存。后面會詳細解釋為啥具有上述兩個特點的程序大頁內(nèi)存無效。
1. 背景
?????? 近期一直在公司從事聽歌識曲項目的開發(fā),詳細內(nèi)容可參考:基于指紋的音樂檢索,目前已上線到搜狗語音云開放平臺。 在開發(fā)的過程中,遇到一個很嚴重的性能問題,單線程測試的時候性能還能達到要求,但是在多線程進行壓力測試的時候,算法最耗時的部分突然變慢了好幾倍!后 來經(jīng)過仔細調(diào)試,發(fā)現(xiàn)最影響性能的居然是一個編譯選項-pg,去掉它之后性能會好很多,但是還是會比單線程的性能慢2倍左右,這就會導(dǎo)致系統(tǒng)的實時率達到 1.0以上,響應(yīng)能力嚴重下降。
?????? 通過更加仔細的分析,我們發(fā)現(xiàn)系統(tǒng)最耗時的部分是訪問指紋庫的過程,但是這部分根本就沒有優(yōu)化余地,只能換用內(nèi)存帶寬更高的機器。換用內(nèi)存帶寬更高的機器 確實帶來了不少性能的提升,但是還是無法達到要求。就在山重水盡的情況下,無意中看到MSRA的洪春濤博士在微博中提到他們用大頁內(nèi)存對一個隨機數(shù)組的訪問問題進行優(yōu)化獲得了很好的性能提升。然后就向他求助,終于通過大頁內(nèi)存這種方法使系統(tǒng)性能進一步提升,實時率也降到了0.4左右。圓滿達成目標!
2. 基于指紋的音樂檢索簡介
檢索過程其實和搜索引擎一樣,音樂指紋就和搜索引擎中的關(guān)鍵詞等價,指紋庫就等價于搜索引擎的后臺網(wǎng)頁庫。指紋庫的構(gòu)造和搜索引擎的網(wǎng)頁庫也是一樣,采用倒排索引形式。如下圖:
圖1 基于指紋的倒排索引表
只不過指紋都是一個int型的整數(shù)(圖示只占用了24位),包含的信息太少,所以需要提取很多個指紋完成一次匹配,大約是每秒幾千個的樣子。每獲得一個指紋都需要訪問指紋庫獲得對應(yīng)的倒排列表,然后再根據(jù)音樂id構(gòu)造一個正排列表,用來分析哪首音樂匹配上,如下圖:
圖2 統(tǒng)計匹配的相似度
最終的結(jié)果就是排序結(jié)果最高的音樂。
目前指紋庫大約60G,是對25w首歌提取指紋的結(jié)果。每一個指紋對應(yīng)的倒排列表長度不固定,但是有上限7500。正排列表的音樂個數(shù)也是25w,每一首音樂對應(yīng)的最長時間差個數(shù)為8192。單次檢索的時候會生成大約1000個左右的指紋(甚至更多)。
通過上面的介紹,可以看出基于指紋的音樂檢索(聽歌識曲)共有三部分:1.提取指紋;2.訪問指紋庫;3.排序時間差。多線程情況下,這三部分的時 間耗費比例大約是:1%、80%和19%,也即大部分時間都耗費在查找指紋庫的操作上。更麻煩的一點是,指紋庫的訪問全部是亂序訪問,沒有一點局部性可 言,所以cache一直在缺失,常規(guī)的優(yōu)化方法都無效,只能換成內(nèi)存帶寬更高的服務(wù)器。
不過正是由于上述的特點—耗費內(nèi)存巨大(100G左右)、亂序訪存且訪存是瓶頸,導(dǎo)致大頁內(nèi)存特別適合來優(yōu)化上面遇到的性能瓶頸問題。
3. 原理
大頁內(nèi)存的原理涉及到操作系統(tǒng)的虛擬地址到物理地址的轉(zhuǎn)換過程。操作系統(tǒng)為了能同時運行多個進程,會為每個進程提供一個虛擬的進程空間,在32位操 作系統(tǒng)上,進程空間大小為4G,64位系統(tǒng)為2^64(實際可能小于這個值)。在很長一段時間內(nèi),我對此都非常疑惑,這樣不就會導(dǎo)致多個進程訪存的沖突 嗎,比如,兩個進程都訪問地址0x00000010的時候。事實上,每個進程的進程空間都是虛擬的,這和物理地址還不一樣。兩個進行訪問相同的虛擬地址, 但是轉(zhuǎn)換到物理地址之后是不同的。這個轉(zhuǎn)換就通過頁表來實現(xiàn),涉及的知識是操作系統(tǒng)的分頁存儲管理。
分頁存儲管理將進程的虛擬地址空間,分成若干個頁,并為各頁加以編號。相應(yīng)地,物理內(nèi)存空間也分成若干個塊,同樣加以編號。頁和塊的大小相同。假設(shè)每一頁的大小是4K,則32位系統(tǒng)中分頁地址結(jié)構(gòu)為:
為了保證進程能在內(nèi)存中找到虛擬頁對應(yīng)的實際物理塊,需要為每個進程維護一個映像表,即頁表。頁表記錄了每一個虛擬頁在內(nèi)存中對應(yīng)的物理塊號,如圖三。在配置好了頁表后,進程執(zhí)行時,通過查找該表,即可找到每頁在內(nèi)存中的物理塊號。
在操作系統(tǒng)中設(shè)置有一個頁表寄存器,其中存放了頁表在內(nèi)存的始址和頁表的長度。進程未執(zhí)行時,頁表的始址和頁表長度放在本進程的PCB中;當調(diào)度程序調(diào)度該進程時,才將這兩個數(shù)據(jù)裝入頁表寄存器。
當進程要訪問某個虛擬地址中的數(shù)據(jù)時,分頁地址變換機構(gòu)會自動地將有效地址(相對地址)分為頁號和頁內(nèi)地址兩部分,再以頁號為索引去檢索頁表,查找 操作由硬件執(zhí)行。若給定的頁號沒有超出頁表長度,則將頁表始址與頁號和頁表項長度的乘積相加,得到該表項在頁表中的位置,于是可以從中得到該頁的物理塊地 址,將之裝入物理地址寄存器中。與此同時,再將有效地址寄存器中的頁內(nèi)地址送入物理地址寄存器的塊內(nèi)地址字段中。這樣便完成了從虛擬地址到物理地址的變 換。
圖3 頁表的作用
由于頁表是存放在內(nèi)存中的,這使CPU在每存取一個數(shù)據(jù)時,都要兩次訪問內(nèi)存。第一次時訪問內(nèi)存中的頁表,從中找到指定頁的物理塊號,再將塊號與頁 內(nèi)偏移拼接,以形成物理地址。第二次訪問內(nèi)存時,才是從第一次所得地址中獲得所需數(shù)據(jù)。因此,采用這種方式將使計算機的處理速度降低近1/2。
為了提高地址變換速度,可在地址變換機構(gòu)中,增設(shè)一個具有并行查找能力的特殊高速緩存,也即快表(TLB),用以存放當前訪問的那些頁表項。具有快表的地址變換機構(gòu)如圖四所示。由于成本的關(guān)系,快表不可能做得很大,通常只存放16~512個頁表項。
上述地址變換機構(gòu)對中小程序來說運行非常好,快表的命中率非常高,所以不會帶來多少性能損失,但是當程序耗費的內(nèi)存很大,而且快表命中率不高時,那么問題來了。
圖4 具有快表的地址變換機構(gòu)
4. 小頁的困境
?????? 現(xiàn)代的計算機系統(tǒng),都支持非常大的虛擬地址空間(2^32~2^64)。在這樣的環(huán)境下,頁表就變得非常龐大。例如,假設(shè)頁大小為4K,對占用40G內(nèi)存 的程序來說,頁表大小為10M,而且還要求空間是連續(xù)的。為了解決空間連續(xù)問題,可以引入二級或者三級頁表。但是這更加影響性能,因為如果快表缺失,訪問 頁表的次數(shù)由兩次變?yōu)槿位蛘咚拇巍S捎诔绦蚩梢栽L問的內(nèi)存空間很大,如果程序的訪存局部性不好,則會導(dǎo)致快表一直缺失,從而嚴重影響性能。
?????? 此外,由于頁表項有10M之多,而快表只能緩存幾百頁,即使程序的訪存性能很好,在大內(nèi)存耗費情況下,快表缺失的概率也很大。那么,有什么好的方法解決快 表缺失嗎?大頁內(nèi)存!假設(shè)我們將頁大小變?yōu)?G,40G內(nèi)存的頁表項也只有40,快表完全不會缺失!即使缺失,由于表項很少,可以采用一級頁表,缺失只會 導(dǎo)致兩次訪存。這就是大頁內(nèi)存可以優(yōu)化程序性能的根本原因—快表幾乎不缺失!
?????? 在前面我們提到如果要優(yōu)化的程序耗費內(nèi)存很少,或者訪存局部性很好,大頁內(nèi)存的優(yōu)化效果就會很不明顯,現(xiàn)在我們應(yīng)該明白其中緣由。如果程序耗費內(nèi)存很少, 比如只有幾M,則頁表項也很少,快表很有可能會完全緩存,即使缺失也可以通過一級頁表替換。如果程序訪存局部性也很好,那么在一段時間內(nèi),程序都訪問相鄰 的內(nèi)存,快表缺失的概率也很小。所以上述兩種情況下,快表很難缺失所以大頁內(nèi)存就體現(xiàn)不出優(yōu)勢來。
5. 大頁內(nèi)存的配置和使用
?????? 網(wǎng)上很多資料在介紹大頁內(nèi)存的時候都會伴隨它在Oracle數(shù)據(jù)庫中的使用,這會讓人產(chǎn)生一種錯覺:大頁內(nèi)存只能在Oracle數(shù)據(jù)庫中使用。通過上面的 分析,我們可以知道,其實大頁內(nèi)存是一種很通用的優(yōu)化技術(shù)。它的優(yōu)化方法就是避免快表缺失。那么怎么具體應(yīng)用呢,下面詳細介紹使用的步驟。
1. 安裝libhugetlbfs庫
?????? libhugetlbfs庫實現(xiàn)了大頁內(nèi)存的訪問。安裝可以通過apt-get或者yum命令完成,如果系統(tǒng)沒有該命令,還可以從官網(wǎng)下載。
2. 配置grub啟動文件
????? 這一步很關(guān)鍵,決定著你分配的每個大頁的大小和多少大頁。具體操作是編輯/etc/grub.conf文件,如圖五所示。
圖5 grub.conf啟動腳本
具體就是在kernel選項的最后添加幾個啟動參數(shù):transparent_hugepage=never default_hugepagesz=1G hugepagesz=1Ghugepages=123。 這四個參數(shù)中,最重要的是后兩個,hugepagesz用來設(shè)置每頁的大小,我們將其設(shè)置為1G,其他可選的配置有4K,2M(其中2M是默認)。如果操 作系統(tǒng)版本太低的情況下,可能會導(dǎo)致1G的頁設(shè)置失敗,所以設(shè)置失敗請查看自己操作系統(tǒng)的版本。hugepages用來設(shè)置多少頁大頁內(nèi)存,我們的系統(tǒng)內(nèi) 存是128G,現(xiàn)在分配123G用來專門服務(wù)大頁。這里需要注意,分配完的大頁對常規(guī)程序來說是不可見的,例如我們的系統(tǒng)還剩余5G的普通內(nèi)存,這時我如 果按照常規(guī)方法啟動一個耗費10G的程序就會失敗。修改完grub.conf后,重啟系統(tǒng)。然后運行命令cat /proc/meminfo|grep Huge命令查看大頁設(shè)置是否生效,如果生效,將會顯示如下內(nèi)容:
圖6 當前的大頁耗費情況
我們需要關(guān)注其中的四個值,HugePages_Total表示目前總共有多少個大頁,HugePages_Free表示程序運行起來之后還剩余多少個大頁,HugePages_Rsvd表示系統(tǒng)當前總共保留的HugePages數(shù)目,更具體點就是指程序已經(jīng)向系統(tǒng)申請,但是由于程序還沒有實質(zhì)的HugePages讀寫操作,因此系統(tǒng)尚未實際分配給程序的HugePages數(shù)目。Hugepagesize表示每個大頁的大小,在此為1GB。
?????? 我們在實驗中發(fā)現(xiàn)一個問題,Free的值和Rsvd的值可能和字面意思不太一樣。如果一開始我們申請的大頁不足以啟動程序,系統(tǒng)就會提示如下錯誤:
ibhugetlbfs:WARNING: New heap segment map at 0x40000000 failed: Cannot allocate memory
此時,再次查看上述四個值會發(fā)現(xiàn)這樣的情況:HugePages_Free等于a,HugePages_Rsvd等于a。這讓人感到很奇怪,明明還 有剩余的大頁,但是系統(tǒng)報錯,提示大頁分配失敗。經(jīng)過多次嘗試,我們認為Free中應(yīng)該是包括Rsvd的大頁的,所以當Free等于Rsvd的時候其實已 經(jīng)沒有可用的大頁了。Free減Rsvd才是真正能夠再次分配的大頁。例如,在圖六中還有16個大頁可以被分配。
具體應(yīng)該分配多少個大頁合適,這個需要多次嘗試,我們得到的一個經(jīng)驗是:子線程對大頁的使用很浪費,最好是所有的空間都在主線程分配,然后再分配給各個子線程,這樣會顯著減少大頁浪費。
3. mount
執(zhí)行mount,將大頁內(nèi)存映像到一個空目錄。可以執(zhí)行下述命令:
[plain] view plaincopyprint?4. 運行應(yīng)用程序
為了能啟用大頁,不能按照常規(guī)的方法啟動應(yīng)用程序,需要按照下面的格式啟動:
HUGETLB_MORECORE=yesLD_PRELOAD=libhugetlbfs.so ./your_program
這種方法會加載libhugetlbfs庫,用來替換標準庫。具體的操作就是替換標準的malloc為大頁的malloc。此時,程序申請內(nèi)存就是大頁內(nèi)存了。
按照上述四個步驟即可啟用大頁內(nèi)存,所以啟用大頁還是很容易的。
6. 大頁內(nèi)存的優(yōu)化效果
如果你的應(yīng)用程序亂序訪存很嚴重,那么大頁內(nèi)存會帶來比較大的收益,正好我們現(xiàn)在做的聽歌識曲就是這樣的應(yīng)用,所以優(yōu)化效果很明顯,下面是曲庫為25w時,啟用大頁和不啟用大頁的程序性能。
可以看出,啟用大頁內(nèi)存之后,程序的訪問時間顯著下降,性能提升接近50%,達到了性能要求。
7. 大頁內(nèi)存的使用場景
任何優(yōu)化手段都有它適用的范圍,大頁內(nèi)存也不例外。前面我們一直強調(diào),只有耗費的內(nèi)存巨大、訪存隨機而且訪存是瓶頸的程序大頁內(nèi)存才會帶來很明顯的 性能提升。在我們的聽歌識曲系統(tǒng)中,耗費的內(nèi)存接近100G,而且內(nèi)存訪問都是亂序訪問,所以才帶來明顯的性能提升。網(wǎng)上的例子一直在用Oracle數(shù)據(jù) 庫作為例子不是沒有道理的,這是因為Oracle數(shù)據(jù)庫耗費的內(nèi)存也很巨大,而且數(shù)據(jù)庫的增刪查改也缺乏局部性。數(shù)據(jù)庫背后的增刪查改基本上是對B樹進行 操作,樹的操作一般缺少局部性。
什么樣的程序局部性較差呢?我個人認為采用哈希和樹策略實現(xiàn)的程序往往具有較差的訪存局部性,這時如果程序性能不好可以嘗試大頁內(nèi)存。相反,單純的 數(shù)組遍歷或者圖的廣度遍歷等操作,具有很好的訪存局部性,采用大頁內(nèi)存很難獲得性能提升。本人曾經(jīng)嘗試在搜狗語音識別解碼器上啟用大頁內(nèi)存,希望可以獲得 性能提升,但是效果令人失望,沒有提升反而導(dǎo)致性能降低。這是因為語音識別解碼器從本質(zhì)上來講就是一個圖的廣搜,具有很好的訪存局部性,而且訪存不是性能 瓶頸,這時采用大頁內(nèi)存可能會帶來其他開銷,導(dǎo)致性能下降。
8. 總結(jié)
本博客以聽歌識曲的例子詳細介紹了大頁內(nèi)存的原理和使用方法。由于大數(shù)據(jù)的興盛,目前應(yīng)用程序處理的數(shù)據(jù)量越來越大,而且數(shù)據(jù)的訪問越來越不規(guī)整, 這些條件給大頁內(nèi)存的使用帶來了可能。所以,如果你的程序跑得慢,而且滿足大頁內(nèi)存的使用條件,那就嘗試一下吧,反正很簡單又沒損失,萬一能帶來不錯的效 果呢。
轉(zhuǎn)載于:https://www.cnblogs.com/dongzhiquan/p/5043912.html
總結(jié)
以上是生活随笔為你收集整理的大页内存(HugePages)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于异常“The 'Microsoft.
- 下一篇: 属性内存管理