LWN:替換 congestion_wait()!
關(guān)注了就能看到更多這么棒的文章哦~
Replacing congestion_wait()
By Jonathan Corbet
October 25, 2021
DeepL assisted translation
https://lwn.net/Articles/873672/
內(nèi)存管理在很多方面都是在追求平衡。例如,內(nèi)核必須要對當前用戶對內(nèi)存的需求以及預期未來的需求之間進行一下權(quán)衡。內(nèi)核還必須權(quán)衡是否要為其他用途而回收內(nèi)存,因為這可能涉及到需要將數(shù)據(jù)寫入永久存儲(permanent storage),以及底層的存儲設(shè)備進行數(shù)據(jù)寫入時的速度等等。多年來,內(nèi)存管理子系統(tǒng)一直把存儲設(shè)備操作的擁塞作為一個信號,告訴它自己應該要放慢回收速度了。不幸的是,這個機制從一開始就有點問題,在很長一段時間內(nèi)其實并沒有效果。Mel Gorman 現(xiàn)在正試圖提供一個 patch set 來解決這個問題,不過這樣一來內(nèi)核就不應該再等待擁塞出現(xiàn)了。
The congestion_wait() surprise
當內(nèi)存變得緊張時,內(nèi)存管理子系統(tǒng)必須得回收當前正在使用的 page 用于其他用途。這反過來又先需要把所有被修改過的 page 的內(nèi)容寫出去。如果要寫入這個 page 內(nèi)容的塊設(shè)備已經(jīng)被繁重的寫入任務所占用了,那么這里就算讓 I/O 請求堆積得更高,也沒有什么效果了。早在黑暗和遙遠的時代, Git 出現(xiàn)之前(2002 年),Andrew Morton 就提議為塊設(shè)備增加一個擁塞追蹤(congestion-tracking)機制:如果某個設(shè)備已經(jīng)擁塞住了,那么內(nèi)存管理子系統(tǒng)就會暫時不再發(fā)起新的 I/O 請求(并控制(也就是放慢)那些在申請更多內(nèi)存的進程的運行),直到擁塞緩解。這一機制在 2002 年 9 月的 2.5.39 開發(fā)版內(nèi)核中就位了。
在那以后的幾年里,擁塞等待機制以各種方式演進。即將發(fā)布的 5.15 內(nèi)核中仍然包含有一個叫做 congestion_wait() 的函數(shù),它可以暫停當前的 task,直到擁擠的設(shè)備變得不那么擁擠(也就是通過 clear_bdi_congested() 調(diào)用來通知這個狀態(tài))或者出現(xiàn) timeout 從而結(jié)束。或者,至少這曾是它想象中的行為。
碰巧,clear_bdi_congested() 的主要調(diào)用位置是一個叫做 blk_clear_congested() 的函數(shù),該函數(shù)在 2018 年的 5.0 內(nèi)核版本中被移除了。從那時起,除了少數(shù)文件系統(tǒng)(Ceph、FUSE 和 NFS)之外,沒有任何東西調(diào)用 clear_bdi_congested() 了,這意味著對 congestion_wait()的調(diào)用幾乎總是會坐等到 timeout 結(jié)束,這并不是開發(fā)者的本意。
又過了一年(來到了 2019 年 9 月),內(nèi)存管理開發(fā)人員才搞清楚這一點,這時,block 子系統(tǒng)維護者 Jens Axboe 讓大家知道:
擁堵已經(jīng)不存在了。在我看來,這個機制一直是無效的,因為它天生就是有 race condition 的。我們在傳統(tǒng)的做法中使用過去的 batch 批處理機制來發(fā)出 signal,而這只在一些設(shè)備上有效。
Norton 在他的原始提案中實際上已經(jīng)注意到了擁塞機制的 race condition 問題。某個 task 可以檢查設(shè)備并看到它此時沒有擁塞,但在該任務排進隊列一直到其 I/O 請求得到之前,情況可能會發(fā)生變化。隨著存儲設(shè)備所支持的命令隊列長度的增加,擁塞檢測也越來越難做到準確了。所以塊設(shè)備的開發(fā)者決定在 2018 年擺脫這個概念。不幸的是,當時沒有人告訴內(nèi)存管理開發(fā)人員這件事,從而導致了 Michal Hocko 在事情被報出來之后當時的很不愉快的抱怨。
這是一個不好的例子,就像是一只手不知道另一只手在做什么。多年來,這個問題導致內(nèi)存管理性能受損。但是,內(nèi)核開發(fā)人員往往不會光坐在那里指責和抱怨,相反,他們開始思考如何解決這個問題。他們一定想得很周到,因為這個過程花費了兩年時間,才有 patch 出現(xiàn)。
Moving beyond congestion
Gorman 的 patch set 一開始就指出,"即使擁塞控制這個功能有效,也不能說這是一個好主意"。有許多情況會拖慢 reclaim process (內(nèi)存回收進程)。其中一種情況是有太多的 page 在進行 writeback,導致底層設(shè)備處理不過來。這種情況可能可以被一個(正常工作的)congestion-wait 機制來解決,但其他問題不會被同樣解決了。因此,這個 patch set 刪除了所有的 congestion_wait() 調(diào)用,并采用了一系列啟發(fā)式的方法來取代它們。
在內(nèi)存管理子系統(tǒng)中,有一些地方的需要對 reclaim 進行限流(throttle)。例如,如果 kswapd 線程發(fā)現(xiàn)目前正在寫回的 page 已被標記為需要立即回收,這表明這些 page 在寫入后備存儲之前已經(jīng)是在 LRU 中走完了一輪了。當這種情況發(fā)生時,進行 reclaim 的 task 將會被限流(throttle)一段時間。但并不是在等待那個已經(jīng)不再存在了的 "congestion is done" 的 signal,而是會暫停 reclaim 直到當前 NUMA node 上的 page 已經(jīng)有足夠多的被寫出去了為止。
請注意,一些線程(尤其是內(nèi)核線程和 I/O worker)在這種情況下不會被節(jié)流,可能會需要它們繼續(xù)工作來清理積壓(backlog)。
許多內(nèi)存管理操作,如 compaction 和 page migration,需要對后續(xù)將要操作的 page 給 "隔離(isolate)" 開。在這種情況下,隔離是指從那些 LRU list 中將此 page 刪除。reclaim process 也需要到在寫出去之前隔離 page。如果許多 task 最終會進行 direct reclaim,那么可以會有大量 page 被隔離,從而需要一些時間才能完全完成 reclaim 操作。如果內(nèi)核對 page 進行隔離操作的處理速度比起 page 被回收的速度要快,那么這完全沒有必要,其實是一種浪費。
如果被隔離的 page 數(shù)量過多的話,內(nèi)核本身已經(jīng)會對 reclaim 進行節(jié)流,但這種節(jié)流操作實際上是在等待(或試圖等待)擁塞結(jié)束。戈爾曼指出。"這沒有意義,過度的并行化處理本身與 writeback 或擁塞無關(guān)"。新的代碼改為了使用一個 wait queue,供那些在進行 reclaim 時因隔離太多 page 而被限流的 task 來使用。當隔離 page 的數(shù)量下降或發(fā)生 timeout 時,就會喚醒這些 wait queue。
有時,進行執(zhí)行 reclaim 操作的線程可能會發(fā)現(xiàn)它的工作基本上沒有什么進展:掃描了很多 page,但成功回收的 page 很少。這可能是由于它在掃描 page 存在太多引用,或者其他一些因素。有了這個 patch,那些在 reclaim 方面進展不好的線程將被節(jié)流,一直等到系統(tǒng)中的某個地方取得進展。具體來說,內(nèi)核將一直等待,直到運行中的回收線程成功完成至少 12%的 page 的掃描,然后才會喚醒那些沒有進展的線程。這應該會減少浪費在無效回收這個工作上的時間。
如果由于內(nèi)存太少而導致寫出 dirty page 的時候失敗了,那么 writeback 工作也會被限流。在這種情況下,只有等到一些 page 被成功寫回之后(或者像平常一樣發(fā)生超時),才會取消限流。
大多數(shù)情況下 timeout 時間被設(shè)置為十分之一秒。不過,等待那些被隔離的 page 數(shù)量下降的 timeout 是五十分之一秒,原因是這種情況應該會很快發(fā)生變化。設(shè)置這些 itmeout 的 patch 指出,這些數(shù)值是 "憑空產(chǎn)生的",但在有人找到更好的值之前,可以先開始使用。作為朝這個不斷改進的方向邁出的第一步,在 benchmark 測試結(jié)果顯示 no-progress 這類 timeout 太容易超時之后,就將它改為了 0.5 秒。
這組 patch set 提供了一組詳盡的 benchmark 測試結(jié)果。作為測試的一部分,Gorman 增加了一個新的 "stutterp " test,期望能展示 reclaim 問題。結(jié)果差別很大,但總體上是積極的。例如,一項測試顯示系統(tǒng)的 CPU 時間減少了 89%。戈爾曼總結(jié)說:
最起碼,限流看起來是有效的,wakeup event 會減少最壞情況下的 stall (卡住情況)。timeout 值可能還有一些調(diào)整空間,但這些工作很可能是徒勞的,因為最壞的情況跟 workload、內(nèi)存大小和存儲速度息息相關(guān)。希望進一步改善這組 patch 的話,更好的方法是根據(jù) task 進行 allocation 分配的速率來確定其優(yōu)先級,但這種做法可能會引入非常昂貴的開銷。
這些 patch 到目前為止已經(jīng)經(jīng)歷了五輪修改,發(fā)生了各種變化。很難想象這項工作最終會有什么原因被 mainline 拒絕。畢竟當前 kernel 中的代碼顯然是無法使用的。但是這種核心的內(nèi)存管理代碼的改動總是很難得到 merge 的。現(xiàn)在世界上的 workload 種類那么多,當改為使用這種啟發(fā)式方法進行處理之后,肯定有一些情況下的性能會下降。因此,雖然這樣的改動看起來會被接受,但人們永遠不知道在合入之前會出現(xiàn)多少次 timeout。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
