LWN:近期在討論的一些內(nèi)存管理相關(guān)patch!
關(guān)注了就能看到更多這么棒的文章哦~
Some upcoming memory-management patches
By Jonathan Corbet
November 12, 2021
DeepL assisted translation
https://lwn.net/Articles/875587/
內(nèi)存管理子系統(tǒng)仍然是內(nèi)核中最復雜的部分之一,為了提升性能,它總是依賴于各種啟發(fā)式規(guī)則。因此不出意料,開發(fā)人員仍在繼續(xù)嘗試改進這部分的功能。目前有許多內(nèi)存管理 patch 在討論中。繼續(xù)閱讀,來了解一下頁表頁(page-table pages)的 free、kvmalloc() flag、內(nèi)存清除(memory clearing)以及 NUMA "home node" 等的進展。
Freeing page-table pages
每當用戶空間在分配內(nèi)存時,內(nèi)核自然必須要找到一些 page 來滿足這個分配內(nèi)存請求。但是內(nèi)核同時也必須要分配一些 page-table page 來將這部分新分配的內(nèi)存地址創(chuàng)建映射(mapping )。在一個擁有 4KB 的 page size 以及 64 位地址的系統(tǒng)中,每 512 個普通的內(nèi)存 page 就需要一個 page-table page(如果沒有使用 huge page 的話)。對于那些具有非常龐大的地址空間的應(yīng)用程序來說,僅僅是用在 page-table page 上的內(nèi)存數(shù)量就很可觀了。
在用戶空間要用的 page 被回收的時候,在一些比如進行了 madvise() 之類調(diào)用的情況下,這些內(nèi)存最終會被浪費掉。也就是說用戶空間的 page 實際上從 working set 移除掉了,但是用來給它們創(chuàng)建 mapping 的這些 page-table page 繼續(xù)保持占用狀態(tài)。如果某個 page-table page 中映射了的所有 page 都被回收了的話,那么 page-table page 本身就是空的,沒有任何作用。進行了大量內(nèi)存分配然后又釋放掉這些內(nèi)存的應(yīng)用程序就會導致很多沒有用處的 page-table page 積累起來,這不是人們期望的狀態(tài)。
來自 Qi Zheng 的一組 patch 就希望能解決這個問題。它為 page-table page 增加了一個引用計數(shù)(利用了 struct page 另一個可以重用的字段)。這個 page 的用戶,比如 page-fault handler 等等,會負責遞增這個引用計數(shù),確保自己在工作的過程中這個 page 不會突然消失了。給用戶空間的一個 page 添加一個 page-table entry 的動作也會遞增這個引用計數(shù),而回收一個 page 的時候就會遞減這個引用計數(shù)。等引用計數(shù)下降到零時,內(nèi)核就知道這個 page-table entry 實際上不包含任何 page-table entry,可以被釋放了。
patch set 測試結(jié)果表明對這些空的 page-table page 進行回收就可以釋放出很多內(nèi)存,可供其他用途之用。另一方面,對 page-fault handler 在幾個方面可以感受到影響。當然,這些影響之一就是每次添加 page-table entry 的時候增加了維護引用計數(shù)這個成本。但是對 page-table page 進行 free 這個動作也是有額外開銷的。后續(xù)如果這部分地址空間要再次被使用的時候,就必須要分配一個新的 page-table page 了??偟膩碚f,這個改動會導致在 page-fault 處理流程中的性能下降 1%。
這個開銷可能就會導致一些用戶無法承受了。不過,現(xiàn)在還沒有相關(guān)的 sysctrl 開關(guān)選項來打開或關(guān)閉這種行為。如果這組 patch set 被合并的話,那么所有的系統(tǒng)都會對空閑的 page-table page 進行 free。也許有些場景下,只有有大量的 page-table page 牽涉其中,那么釋放這些內(nèi)存所帶來的性能提升就可以超過 page-fault 這里的性能損失,但對于其他一些類型的應(yīng)用可能就并不適用了。
More flags in vmalloc()
在 kernel 中用 vmalloc() 分配的內(nèi)存必須要被 map 到內(nèi)核地址空間的一個特殊位置。這跟 kmalloc()或 page allocator 分配出來的內(nèi)存并不一樣,無法直接通過內(nèi)核的 direct memory mapping 來進行訪問。曾經(jīng)有一段時間大家不鼓勵使用 vmalloc(),因為 vmalloc() 區(qū)域的地址空間很小,而且創(chuàng)建額外的 mapping 也會帶來額外開銷。不過,隨著時間的推移,使用 vmalloc() 的地方已經(jīng)越來越多了。64 位系統(tǒng)的出現(xiàn)消除了地址空間的限制,而且代碼中需要進行多個 page 的分配的地方也越來越多。如果這個 buffer 中的所有 page 不需要是物理連續(xù)的,那么成功完成分配這個 buffer 的工作的可能性就越高。
然而,vmalloc()接口從沒有支持過那些傳遞給 kmalloc() 的各種 GFP_* 標志(這些是用來調(diào)整內(nèi)存的分配方式的),這個限制也同樣存在于衍生的 kvmalloc() 等函數(shù)中。kvmalloc 函數(shù)會嘗試調(diào)用 kmalloc(),并在失敗時取而代之地采用 vmalloc()。對于一些內(nèi)核子系統(tǒng),尤其是文件系統(tǒng)來說,這是一個真是存在的問題,因為它們都需要能夠在分配內(nèi)存時帶有 GFP_NOFS、GFP_NOIO 或 GFP_NOFAIL 等 flag。因此,一些文件系統(tǒng)中都在避免使用 kvmalloc(),而其他的文件系統(tǒng)比如 Ceph 則推出了自己的內(nèi)存分配函數(shù)來解決這個問題。
Michal Hocko 用一個 patch set 解決了這個問題,他在 vmalloc() 子系統(tǒng)中加入了對上述 GFP_ 各種 flag 的支持,同時也讓 kvmalloc() 支持起來了。這樣一來這些函數(shù)就可以用在文件系統(tǒng)環(huán)境中了,也能刪除掉 Ceph 特有的分配函數(shù)了。截至目前,這組補丁中的一個前提 patch 已經(jīng)進入了 mainline,但其余的還沒有 merge。很可能會在 5.16 合并窗口結(jié)束前完成合入,敬請關(guān)注。
Uncached memory clearing
現(xiàn)代計算機中總是喜歡使用大量的 memory cache 原因很簡單:cache 可以提高性能。因此,看到 Ankur Arora 的這個 patch set 就會感到很有意思,因為它聲稱是通過繞過 cache 來提高內(nèi)存性能。大家也許可以猜到,這是一個只在特定情況下有效果的改動。
如果內(nèi)核需要將某個 memory page 里的內(nèi)容全部都清零,那么肯定是需要依次執(zhí)行一系列普通帶有 cache 支持的寫操作。這樣就可以讓系統(tǒng)自己挑選合適的時機來將 cache 中存放的數(shù)據(jù)寫入到主內(nèi)存中。一個剛剛被清零的 page,很大可能會很快被寫入其他數(shù)據(jù),因此將該 page 放在 cache 中就會加快這些寫入操作的速度,其實可能根本不需要將最初的清零內(nèi)容寫入內(nèi)存中。因此,使用 cache 在這種場景是有性能優(yōu)勢的。
但是當需要對大量的內(nèi)存內(nèi)容進行清零時情況就會發(fā)生變化了。如果需要清零的內(nèi)存數(shù)量超過了最終一級(last level)cache 的大小,那么直接寫入內(nèi)存其實要比等待 cache 內(nèi)容寫到主內(nèi)存去要快。因為這種大量的 write 操作也會把其他內(nèi)容從 cache 中擠出來,而其中一些數(shù)據(jù)很可能在不久的將來就會需要直接使用了。所以,對于這種大范圍的清零操作,更好的方法還是應(yīng)該繞過 cache。
因此,Arora 的 patch set 修改了內(nèi)核,當需要對一個 huge page(2MB)或 gigantic page(1GB)進行清零操作時,就使用 uncached write。這類情況經(jīng)常發(fā)生在運行虛擬化 guest 的系統(tǒng)中,一個新的 guest 在最開始時需要基于 host 提供的大片清零過的內(nèi)存區(qū)域,并且這些區(qū)域會是盡可能使用 huge page 或 gigantic page 管理的。這組 patch set 的測試結(jié)果顯示,虛擬機創(chuàng)建時的性能提高了 1.6 倍到 2.7 倍。這應(yīng)該足以說服大家接受這個改動了。
Setting a home NUMA node
NUMA 系統(tǒng)的特點是訪問位于本地 NUMA node(或附近的 node)的內(nèi)存要比訪問遠程節(jié)點的內(nèi)存速度更快。這意味著我們可以通過控制哪些節(jié)點用來做內(nèi)存分配,這樣可能會有很大范圍上的性能提升。內(nèi)核提供了許多控制這類分配的操作,包括最近增加的 MPOL_PREFERRED_MANY,LWN 在 7 月份已經(jīng)介紹過。
不過,Aneesh Kumar K.V.想再添加一個新的系統(tǒng)調(diào)用:
int set_mempolicy_home_node(unsigned long start, unsigned long len,
unsigned long home_node, unsigned long flags);
這個系統(tǒng)調(diào)用將把 home_node 參數(shù)對應(yīng)的 NUMA node 中從 start 開始的 len 字節(jié)的地址范圍稱為 "home node"。目前,flags 參數(shù)還沒有使用。
home node 這個概念需要與 MPOL_PREFERRED_MANY 或 MPOL_BIND 內(nèi)存分配策略結(jié)合使用。這些策略可以指定一組用于進行新的內(nèi)存分配請求的 node,但并不指定哪一個節(jié)點是首選的分配 node。如果用 set_mempolicy_home_node() 設(shè)置了一個 home node,那么只要有可能,都會盡量在該 node 上進行分配。如果失敗的話,內(nèi)核就會根據(jù) in-force policy 中所允許的 node 來 fallback 到相應(yīng) node 上進行分配,會優(yōu)先選擇離 home node 最近的 node。
目的是讓應(yīng)用程序?qū)?nèi)存分配擁有更多的掌控,同時避免使用 slow node 的內(nèi)存。目前還沒有提及 NUMA 開發(fā)者們何時會放棄當前的做法,改為完全由用戶空間提供的 BPF 程序來決定 memory-allocation 策略。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
