程式設計師的 CPU 微架構

大頁/巨型頁(huge / large pages)

虛擬記憶體的預設單位是 4 KiB 的頁,而 TLB 一次只能記住固定數目的頁對映。所以若每個對映只涵蓋 4 KiB,你的轉譯在發生 TLB 失誤前所能涵蓋的記憶體總量就很小。大頁從另一個方向解決此事:不去對映更多項目,而是讓每個項目涵蓋遠多的記憶體。大頁就是單一一頁,但遠大於 4 KiB——常見 2 MiB,在 x86-64 上有時是 1 GiB。

回報是 TLB 涵蓋範圍上的一個乘數。一個 2 MiB 頁的 TLB 項目,涵蓋的定址空間是 4 KiB 項目的 512 倍,因此在大區域上工作的程式,需要遠少的 TLB 項目去對映它,遭受遠少的 TLB 失誤,也觸發遠少的分頁表走訪(分頁表樹也更淺,因為一片大葉子取代了一整棵小葉子的子樹)。取得大頁的方式有兩種:明確地——Linux 透過 hugetlbfs、給 mmap() 的 MAP_HUGETLB 旗標、或從保留的大頁池配置的函式庫來提供;或透通地——核心的「透通大頁(Transparent Huge Pages)」功能會在背景悄悄把符合條件的 4 KiB 區域升級成 2 MiB 的。

不過要誠實面對取捨。大頁必須實體連續,所以更難配置,尤其在記憶體已碎片化之後——一次保留可能失敗或卡住。它們很粗:對一個 2 MiB 頁做對映、保護或寫入時複製,會一次牽動它全部,這可能浪費記憶體或造成延遲尖峰(透通大頁尤其在歷史上曾因背景壓縮而造成不可預測的停滯,有些對延遲敏感的系統會刻意把它關掉)。大頁是針對「已量測到的、大型工作集上的 TLB 壓力」的定點解藥,而非免費的全域加速——把它用在剖析器顯示有分頁表走訪成本之處,而非到處亂用。

一個在 4 KiB 頁下把 TLB 弄到抖動的 1 GiB 記憶體內索引,需要 262144 個對映;改由 2 MiB 大頁支撐則只需 512 個,輕鬆塞進 L2 TLB,於是分頁表走訪成本趨近於零。在 Linux 上:mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0)。

同樣一個 GiB,對映數少 512 倍——大頁的全部重點就在於 TLB 涵蓋範圍。

大頁只有在 TLB 失誤確實是你的瓶頸時才有幫助;若你的工作集本來就塞得進一般 TLB,它們只會徒增成本(連續性壓力、粗粒度的寫入時複製、可能的壓縮停滯)卻毫無收穫。先量測。

又稱
large pagessuperpagestransparent huge pagesTHP巨頁