THP

THP機制

使用huge page,可以在TLB容量固定的情況下,提高TLB的命中率,即便TLB miss,因爲減少了頁表級數,也可以減少查找頁表的時間。在內存虛擬化中,由於地址轉換需要的級數更多,huge page能發揮的作用就顯得更爲重要。

針對64位的x86-64系統,huge page的大小是2MB或者1GB,初始數目由啓動時的"vm.nr_hugepages" 內核參數確定,對於2MB的huge page,運行過程中可通過"/proc/sys/vm/nr_hugepages"修改。

但對於1GB的huge page,就只能在啓動時分配(且分配後不能釋放),而不支持在運行時修改(系統起來後再要倒騰出1GB連續的物理內存,也怪難爲內核的):

Linux針對huge page提供了一種特殊的hugetlbfs文件系統:

mount -t hugetlbfs hugetlbfs /dev/hugepages

同tmpfs類似,基於hugetlbfs創建文件後,再進行mmap()映射,就可以訪問這些huge page了(使用方法可參考內核源碼"tools/testing/selftests/vm"中的示例)。

【另一種huge page】

在Linux中,除了這種普通的huge page,自2.6.38版本開始支持THP。在應用需要huge page的時候,可通過memory compaction操作移動頁面,以形成一個huge page,因爲該過程不會被應用感知到,所以被稱爲"transparent"。

與之相對的,可以把普通的huge page稱爲"static huge page"。一個是動態的,一個是靜態的。兩者的關係,類似於上文介紹的CMA和預留DMA的關係。如果以向雲廠商購買機器來做個類比的話,靜態huge page就是 dedicate 的物理服務器,底層的硬件資源只屬於你,而THP就是虛擬機,資源是動態調配的。

THP最開始只支持一種huge page的大小(比如2MB),自3.8版本加入這個patch之後,利用shmget()/mmap()的flag參數中未使用的bits(參考這篇文章),可以支持其他的huge page大小(比如1GB)。

【應用的限制】

此外,早期的THP還只支持anonymous pages,而不支持page cache,從它在"/proc/meminfo"中的名字"AnonHugePages"也可以看出來。

這一是因爲anonymous pages通常只能通過mmap映射訪問,而page cache還可以通過直接調用read()和write()訪問,huge page區別於normal page的體現就是少了一級頁錶轉換,不通過映射訪問的話,對huge page的使用就比較困難。

二是如果使用THP的話,需要文件本身足夠大,才能充分利用huge page帶來的好處,而現實中大部分的文件都是比較小的(參考這篇文章)。

不過在某些場景下,讓THP支持page cache的需求還是存在的。實現的方法大致說來有兩種:一種是藉助既有的compoud page的概念實現的支持THP的tmpfs,另一種是使用一個新的表達一組pages的概念,即team page(參考這篇文章)。

選擇tmpfs入手是因爲作爲一個文件系統,它很特殊,從某種意義上說,它不算一個“貨真價實”的文件系統。但它這種模棱兩可的特性正好是一個絕佳的過渡,實現了THP對tmpfs的支持之後,可以進一步推廣到ext4這種標準的磁盤文件系統中去,但……還很多問題要解決,比如磁盤文件系統的readahead機制,預讀窗口通常是128KB,這遠小於一個huge page的大小(參考這篇文章)。

【存在的問題】

靜態huge page擁有一套獨立的內存系統,跟4KB的normal page構成的普通內存可以說是井水不犯河水。而且,靜態huge page也是不支持swap操作的,不能被換出到外部存儲介質上。

THP和靜態huge page看起來樣子差不多,但在Linux中的歸屬和行爲卻完全不同。這麼說吧,後者是一體成型的,而前者就像是焊接起來的。THP雖然勉強拼湊成了huge page的模樣,但骨子裏還是normal page,還和normal page同處一個世界。

在它誕生之初,面對這個龐然大物,既有的內存管理子系統的機制還沒做好充分的應對準備,比如THP要swap的時候怎麼辦啊?這個時候,只能調用split_huge_page(),將THP重新打散成normal page。

swap out的時候打散,swap in的時候可能又需要重新聚合回來,這對性能的影響是不言而喻的。一點一點地找到空閒的pages,然後辛辛苦苦地把它們組合起來,現在到好,一切都白費了(路修了又挖,挖了又修……)。雖然動態地生成huge page確實能更充分利用物理內存,但其帶來的收益,有時還真不見得能平衡掉這一來一去的損耗。

不過呢,內核開發者也在積極努力,希望能夠實現THP作爲一個整體被swap out和swap in(參考這篇文章),但這算是對“牽一髮而動全身”的內存子系統的一次重大調整,所以更多的regression測試還在進行中。

可見啊,THP並沒有想象的那麼美好,用還是不用,怎麼用,就成了一個需要思考和選擇的問題。

【使用策略】

以RedHat的發行版爲例,自RHEL 6開始,THP都是默認打開的,如果要禁止,應該在內核的啓動參數裏設置"transparent_hugepage=never"。系統運行起來後,也可以動態地調整(on-the-fly):

"always"和"never"的意義比較明顯,而"madvise"的意思是隻有顯式地使用了madvise(MADV_HUGEPAGE) 相關的接口,才啓用THP。Linux中使用一個單獨的線程khugepaged來負責實現THP,不管設置爲"always"還是"madvise",khugepaged都是會被啓動的(要時刻做好準備嘛)。

需要注意的是,如果動態地將"enable"更改爲"never" ,則只能保證之後不能生成新的THP了,但之前的THP還會繼續存在,不會被打散爲normal page。

前面的文章說過,memory compaction的意義不僅在於當前能夠分配一段連續的物理內存,還需要未雨綢繆,通過defragmentation操作爲將來的huge page分配提供便利,對此,內核同樣提供了相關的調整參數:

這些選項實際提供了對“抗碎頁”激進程度的把控,比如使用"defer",那麼就會藉助kcompactd內核線程來挑選和移動空閒的normal pages,達到一定數量後,再由khugepaged將這些normal pages合併爲THP。

總的來說,目前THP優劣的平衡還存在一定的不確定性,在虛擬化的應用中,如果物理內存充足,通常建議使用單獨劃分的靜態huge page,而不使用THP(參考華爲鯤鵬平臺的優化建議)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章