LWN:利用BIO caching來提供IO速度!
關(guān)注了就能看到更多這么棒的文章哦~
More IOPS with BIO caching
By Jonathan Corbet
September 6, 2021
DeepL assisted translation
https://lwn.net/Articles/868070/
很久很久以前,塊存儲設(shè)備(block storage device)速度非常慢,經(jīng)常拖慢了整個系統(tǒng)的速度。為了能從存儲設(shè)備中獲得最佳性能,我們花了很大的力氣來對所有的 request 調(diào)整順序。為此而讓 CPU 多耗費一些時間就是值得的。但是后來存儲設(shè)備的速度大增,這個交換就不那么值得了。后來,那些花里胡哨的 I/O 調(diào)度機制不再是人們關(guān)注重點了,現(xiàn)在的工作重點變成了優(yōu)化代碼能使 CPU 跟上存儲設(shè)備的速度。針對 5.15 內(nèi)核最近就合并了一個 block layer 的改動,正好展示了為了從當前硬件中能獲得最佳性能而必須做出哪些權(quán)衡。
在 block layer 中,每個 I/O 操作都是使用一個 struct bio 來代表的。這種結(jié)構(gòu)的每一個實例通常都被稱為 "BIO"。BIO 中包含了一個指向相關(guān) block 設(shè)備的指針、指明即將要傳輸哪些 buffer、一個指向在操作完成后所要調(diào)用的函數(shù)指針以及大量的的輔助信息。針對系統(tǒng)中發(fā)起的每一個 I/O 操作,都需要分配一個 BIO、管理維護好、最終釋放掉。對于那些擁有速度飛快的 block device 的大型、繁忙系統(tǒng)來說,每秒鐘可以產(chǎn)生數(shù)百萬次的 I/O 操作(IOPS),因此有大量的 BIO 結(jié)構(gòu)在源源不斷地進行這個從生到死的循環(huán)。
內(nèi)核中的 slab allocator 針對這種重復(fù)分配和釋放相同大小的結(jié)構(gòu)的場景進行了優(yōu)化。看起來它應(yīng)該最適合作為 block 子系統(tǒng)中分配的 BIO 的來源。但事實證明 slab allocator 的速度還不夠快,它已經(jīng)成為拖慢 block I/O 的瓶頸了。因此,block 子系統(tǒng)維護者 Jens Axboe 準備一組 patch 來解決這個問題。
他實現(xiàn)的是對 BIO 結(jié)構(gòu)的一個簡單緩存(cache)。它是由一組 linked list(鏈表)組成的。系統(tǒng)中每個 CPU 都有一個這樣的 linked list。每當需要一個新的 BIO 時(并且需要滿足其他一些條件,詳見下文),都會檢查當前 CPU 中的鏈表。只要在那里發(fā)現(xiàn)有空閑的 BIO,那么就可以從鏈表中移出來而直接使用,就不必再調(diào)用 slab allocator 了。如果鏈表是空的,那當然只能跟往常一樣通過 slab 進行分配了。在需要釋放一個 BIO 的時候,它會被加入到當前 CPU 的鏈表中去。如果鏈表變得太大了(超過了 576 個 cached BIO),那么就會把 64 個 BIO 一次性交還給 slab allocator。
這個機制很簡單,這也是它為什么速度很快的原因了。block layer 就不需要調(diào)用 slab allocator 了,而是直接從相應(yīng)的 per CPU 鏈表中獲取可用的 BIO,都不需要調(diào)用任何函數(shù)了。使用了 per-CPU 鏈表就可以避免使用 lock 機制,這進一步提高了速度。鏈表的管理模式就跟堆棧一樣,這也使得所分配出來的 BIO 還存在于 CPU cache 中的可能性大大提高了。最終都帶來了顯著的性能提升。
至少對于某些工作負載來說是看到很大性能提升的。如前所述,BIO cache 機制很簡單,所以它沒有確保發(fā)生中斷時也是安全的。per-CPU 的數(shù)據(jù)結(jié)構(gòu)只有在 kernel 執(zhí)行 critical section 時不會被搶占的情況下,才可以確保不用 lock 也是安全的。而 interrupt 當然也是一種 preemption(搶占),在這種情況下就會導(dǎo)致問題。如果一個 block-driver 的中斷處理程序 (interrupt handler) 正在分配或釋放一個 BIO,而此時有其他的內(nèi)核代碼也在做類似的動作,那么很可能會出問題,而用戶也就不會因為提升這些性能而感激作者。
當然,BIO cache 可以做成是不怕 interrupt 的,而且后面也許有一點就會實現(xiàn)成這樣。但是禁用中斷也會帶來性能損失,這是一個不這么實現(xiàn)的很好的理由。如果不關(guān)閉 interrupt 的話,BIO cache 就只能在那些絕對不會在 interrupt handler 同時進行調(diào)用的情況下使用了。好消息是,這里有一種情況下這一點是可以得到保證的,那就是當使用 block-layer I/O polling 的時候。polling 操作會先關(guān)閉來自存儲設(shè)備的 interrupt,完全只在這里循環(huán)操作直到 I/O 請求完成為止。對于那些快速設(shè)備(fast device)來說,這實際上是很合理的做法。在非常希望能盡量提高 I/O 速率的情況下,系統(tǒng)管理員很可能會啟用 polling。因此針對這個場景再提供一些額外的性能提升,那就會是很有意義的。
把 slab allocator 的動作從這個循環(huán)操作中移除掉,就可以大大改善性能,但這里還有一個問題需要改正。block layer 有一個叫做 bio_init() 的函數(shù),其中主要有下面這個操作:
memset(bio, 0, sizeof(*bio));
人們可能認為 memset() 是對這樣一個中等大小的結(jié)構(gòu)進行初始化的最快速的方法了,但事實證明并非如此。所以 Axboe 還多做了一個 patch,用一系列直接對 BIO 中每個 field 設(shè)置為 0 的操作替換了 memset() 調(diào)用。changelog 中指出,這個改動使得分配加上初始化 BIO 的總時間減少了一半(當然是在使用 BIO cache 的情況下)。
Axboe 說,在這些改動都到位之后,block layer 的性能提高了大約 10%。在他的測試系統(tǒng)中每個 CPU core 上都可以達到超過 350 萬 IOPS 了。這里節(jié)省了大量的 block 分配釋放,肯定會讓管理 storage server 的經(jīng)理們非常高興了。這組 patch 展示了在當前硬件上要想優(yōu)化 I/O 吞吐量可以做(而且必須做)哪些事情。但它也表明,可能是時候要在這些已經(jīng)高度優(yōu)化的 slab allocator 上投入更多精力來進行優(yōu)化了。如果內(nèi)核開始針對每個子系統(tǒng)中的對象都做緩存,從而繞過 allocator,那么系統(tǒng)的整體性能就會受到影響。同時,基本上所有人都會喜歡這種提高 block-I/O 性能的做法。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
