鄧通亮,陳宸,殷樹
(1 上??萍即髮W(xué)信息科學(xué)與技術(shù)學(xué)院,上海 201210;2 中國科學(xué)院上海微系統(tǒng)與信息技術(shù)研究所,上海 200050;3 中國科學(xué)院大學(xué),北京 100049)(2020年3月23日收稿;2020年5月11日收修改稿)
在高性能計算系統(tǒng)中,并行文件系統(tǒng)的性能表現(xiàn)起著至關(guān)重要的作用[1-3]。目前存在不少的并行文件系統(tǒng),例如PVFS[4]、PLFS[5]和Lustre[6]等。高性能計算系統(tǒng)由多個部分組成,應(yīng)用的工作負荷往往復(fù)雜多變,系統(tǒng)中的薄弱環(huán)節(jié)易成為性能瓶頸,需要對其進行針對性的優(yōu)化。例如,文獻[7]對并行文件系統(tǒng)的小文件性能進行優(yōu)化,文獻[8]對文件系統(tǒng)中元數(shù)據(jù)的訪問進行優(yōu)化。
一般地,文件系統(tǒng)(例如 ext4)在操作系統(tǒng)內(nèi)核中實現(xiàn)。但是,由于開發(fā)內(nèi)核態(tài)文件系統(tǒng)難度大,不少開發(fā)者選擇使用FUSE[9](filesystem in userspace,用戶態(tài)文件系統(tǒng)框架)作為文件系統(tǒng)和內(nèi)核之間的中間層,在用戶態(tài)開發(fā)文件系統(tǒng)[10-11]。通過FUSE,使用者可以在不修改內(nèi)核的情況下部署文件系統(tǒng),開發(fā)者可以在不進入內(nèi)核的情況下調(diào)試文件系統(tǒng)。常見的基于FUSE的用戶態(tài)文件系統(tǒng)有PLFS[5]、GlusterFS[12]和Sshfs[13]等。
然而,在基于FUSE實現(xiàn)的用戶態(tài)文件系統(tǒng)中,由于FUSE的介入,處理每一個I/O請求都會額外引入多次用戶態(tài)和內(nèi)核態(tài)之間的運行態(tài)切換(下文簡稱:運行態(tài)切換)、上下文切換和內(nèi)存拷貝,加上FUSE內(nèi)部的等待隊列和I/O請求重排,存在一定的性能損失。Bent等[5]發(fā)現(xiàn)FUSE能夠造成20%左右的性能下降。Vangoor等[14]研究了ext4上FUSE的性能特點,表明FUSE在不同的工作負荷下導(dǎo)致不同程度的性能下降,最大下降83%。最近研究者發(fā)現(xiàn)Intel 處理器中存在Meltdown[15]等一系列漏洞,操作系統(tǒng)發(fā)布的補丁程序增強了用戶態(tài)和內(nèi)核態(tài)內(nèi)存空間的隔離性,導(dǎo)致運行態(tài)切換的開銷增加。
為優(yōu)化用戶態(tài)文件系統(tǒng)的性能,Ishiguro等[16]提出一種通過FUSE的內(nèi)核模塊直接與內(nèi)核文件系統(tǒng)交互的方法,優(yōu)化用戶態(tài)文件系統(tǒng)的性能,但該方法需要對內(nèi)核進行修改,可移植性較差。Zhu等[17]提出一種將FUSE移植到用戶空間的方法,雖然消除了運行態(tài)切換和上下文切換,但是由于只有基于libsysio[18]API 進行開發(fā)的應(yīng)用程序才能使用該方法,存在一定的可擴展性問題。Rajgarhia和Gehani[19]通過JNI(Java native interface)實現(xiàn)了基于JAVA的FUSE接口,并且給出性能測試,但是缺乏深入的性能結(jié)果分析。文獻[20]基于特定應(yīng)用的I/O工作負荷特征,對高性能文件系統(tǒng)進行優(yōu)化和深入的結(jié)果分析。
本文利用動態(tài)鏈接技術(shù),在用戶態(tài)并行文件系統(tǒng)中實現(xiàn)了一種繞過FUSE的方法,消除FUSE的性能影響,對比了用戶態(tài)并行文件系統(tǒng)在基于FUSE和繞過FUSE后的性能特點。該方法可以運用于所有動態(tài)鏈接了標(biāo)準(zhǔn)I/O庫的應(yīng)用,不需要對應(yīng)用進行任何修改就可以提高I/O性能。實驗結(jié)果表明,該方法可以顯著提高I/O性能。其中,在傳輸塊較大時,讀性能最大提高131%,寫性能和有FUSE時性能相仿;傳輸塊較小時,寫性能最大提高5倍左右。
文件系統(tǒng)通常在內(nèi)核中實現(xiàn),應(yīng)用的I/O請求通過系統(tǒng)調(diào)用的方式陷入內(nèi)核態(tài),利用其更高的執(zhí)行權(quán)限完成I/O。與此同時,存在一類攔截I/O請求的中間層文件系統(tǒng),它們向應(yīng)用提供文件的抽象,并支持POSIX文件I/O語義,但是不實際存儲文件,而是將文件的內(nèi)容重新組織并存儲到底層存儲系統(tǒng)中,這樣的中間層稱為可堆疊文件系統(tǒng)[21]。FUSE作為應(yīng)用和用戶態(tài)文件系統(tǒng)之間的橋梁攔截并轉(zhuǎn)發(fā)I/O請求,因此可以使用FUSE開發(fā)此類可堆疊文件系統(tǒng)。
圖1[14]展示了FUSE的基本架構(gòu)與操作流程。FUSE由兩部分組成,內(nèi)核模塊和用戶態(tài)守護進程,二者通過虛擬設(shè)備/dev/fuse進行通信。在基于FUSE的文件系統(tǒng)上觸發(fā)I/O請求時,內(nèi)核虛擬文件系統(tǒng)(virtual filesystem, VFS)將該請求轉(zhuǎn)發(fā)至FUSE內(nèi)核模塊,內(nèi)核模塊再將其重定向到對應(yīng)的用戶態(tài)守護進程,守護進程處理該請求,請求結(jié)果沿著原路返回。由此可見,處理1次I/O請求最少需要4次運行態(tài)切換,2次上下文切換和2次內(nèi)存拷貝;如果守護進程涉及本地文件系統(tǒng)的讀寫,那么至少還需要2次運行態(tài)切換和1次內(nèi)存拷貝。傳統(tǒng)的內(nèi)核文件系統(tǒng)處理1次I/O請求一般只需要2次運行態(tài)切換和1次內(nèi)存拷貝,而不需要上下文切換。相比之下,F(xiàn)USE 2次不可避免的上下文切換會帶來較大的性能損失[22]。
圖1 FUSE的基本架構(gòu)[14]
為應(yīng)對軟硬件的錯誤,高性能計算系統(tǒng)會采用各種容錯方案,例如檢查點技術(shù)[23]。通過定期地將中間結(jié)果保存到存儲系統(tǒng),當(dāng)系統(tǒng)發(fā)生錯誤并且被重置后,應(yīng)用可以從最近的檢查點中恢復(fù)狀態(tài)并且繼續(xù)運行。
并行日志文件系統(tǒng)(parallel log-structured file system, PLFS)[5]通過在存儲棧中引入一個中間層的方式對檢查點的寫性能進行優(yōu)化。PLFS重新組織存儲模式,將N-1讀寫模式轉(zhuǎn)成N-N讀寫模式,同時利用日志型文件系統(tǒng)利于寫操作的特點[24],極大地提高了檢查點的寫入性能。PLFS專用于并行I/O,并且進行了大量的優(yōu)化,可以通過FUSE掛載到目錄樹,因此能夠在大量的I/O工作負荷下對FUSE進行測試,充分反映本文所設(shè)計方法的優(yōu)勢與不足。圖2給出了通過FUSE掛載PLFS后的I/O操作流程。
圖2 PLFS通過FUSE的I/O操作流程
PLFS提供了一組API,應(yīng)用可以通過API直接使用PLFS的功能。繞過FUSE的一種方法是將應(yīng)用源代碼中所有I/O相關(guān)的函數(shù)調(diào)用替換成對PLFS API的調(diào)用,然后將源代碼重新編譯得到直接使用PLFS的可執(zhí)行文件。該方法需要得到應(yīng)用的源代碼,并且對其有比較深入的理解,對于大多數(shù)應(yīng)用來說局限性較大。
另一種方法是采用動態(tài)鏈接庫覆蓋用戶態(tài)I/O函數(shù)的方式,將I/O操作重定向到PLFS內(nèi)部。本文基于PLFS API設(shè)計并實現(xiàn)了一個動態(tài)鏈接庫libplfs,通過libplfs能夠在不需要對應(yīng)用進行修改的情況下,達到繞過FUSE的目的。與此同時,在libplfs中收集相關(guān)數(shù)據(jù),分析FUSE性能表現(xiàn)背后的原因。
動態(tài)鏈接庫能夠在運行時被鏈接到進程的地址空間,因此可以定義和I/O調(diào)用具有相同函數(shù)簽名的函數(shù),然后編譯成動態(tài)鏈接庫,利用該動態(tài)鏈接庫對應(yīng)用中的I/O調(diào)用進行高效地重定向,最終實現(xiàn)將I/O請求轉(zhuǎn)發(fā)到PLFS中,把核心操作都放在用戶態(tài)中完成。
LD_PRELOAD[25]環(huán)境變量用于指定最先加載的動態(tài)鏈接庫,讓自定義的動態(tài)鏈接庫擁有甚至比標(biāo)準(zhǔn)庫高的最高優(yōu)先級,達到優(yōu)先加載和鏈接自定義函數(shù)庫的目的。
本文的方法是將PLFS API封裝到libplfs動態(tài)鏈接庫中,利用LD_PRELOAD實現(xiàn)優(yōu)先加載和鏈接libplfs中的I/O庫函數(shù)。
由于POSIX API與PLFS API之間存在差異,上層應(yīng)用通過POSIX文件描述符進行I/O操作,而在PLFS上進行I/O操作所使用的文件描述符具有其自身特點,因此需要在libplfs中實現(xiàn)從POSIX文件描述符到PLFS文件描述符的轉(zhuǎn)換,并且對文件的狀態(tài)進行管理。
本文通過2個內(nèi)存中的全局哈希表(時間復(fù)雜度O(1))實現(xiàn)文件映射關(guān)系的管理,分別為fd_table和path_table。fd_table中的key為上層應(yīng)用所使用的POSIX文件描述符fd,value為plfs_file結(jié)構(gòu)體指針(見圖3)。fd_table的作用是向應(yīng)用提供POSIX文件描述符,保證可以在不對應(yīng)用進行修改的情況下繞過FUSE并且使用PLFS。path_table哈希表用于對同一文件打開多次的情況,結(jié)合引用計數(shù),實現(xiàn)將多個POSIX文件描述符fd映射到同一個plfs_file對象。fd_table中的鍵值對在libplfs中的open函數(shù)內(nèi)完成插入(偽代碼1中第10和16行),在close函數(shù)中進行刪除,在libplfs中重載的其他I/O函數(shù)內(nèi),通過查詢fd_table哈希表實現(xiàn)從POSIX文件描述符到PLFS文件描述符的轉(zhuǎn)換。path_table僅在libplfs中的open函數(shù)(偽代碼1中第8、9、10和15行)和close函數(shù)內(nèi)使用。
以libplfs中重載的open函數(shù)為例(偽代碼1),在打開某一文件時,通過路徑參數(shù)判斷該文件是否位于PLFS文件系統(tǒng)中,如果是,則在tmpfs上創(chuàng)建一個POSIX API所使用的文件描述符fd,然后打開該PLFS文件,利用fd_table和path_table記錄fd與PLFS文件描述符之間的映射關(guān)系,為接下來應(yīng)用通過POSIX文件描述符fd觸發(fā)PLFS上的I/O操作提供翻譯功能。偽代碼1中涉及的Plfs_file結(jié)構(gòu)體中具體成員信息見圖3。
Pseudocode 1 open()function in libplfs
圖3 libplfs的處理流程
libplfs的主要功能是在用戶空間維護一個文件狀態(tài)映射表,將對C標(biāo)準(zhǔn)庫和POSIX I/O函數(shù)的調(diào)用轉(zhuǎn)換成對PLFS API的調(diào)用。libplfs的具體函數(shù)調(diào)用流程見圖3。
本文通過open(), read(), write()和close()這4個具有代表性的I/O函數(shù)對libplfs的實現(xiàn)進行說明。在打開一個文件時,libplfs在本地文件系統(tǒng)上創(chuàng)建一個臨時的虛擬文件,為應(yīng)用進程分配一個文件描述符fd。接著libplfs調(diào)用plfs_open()函數(shù)創(chuàng)建一個PLFS API使用的文件對象plfs_file,并且建立虛擬文件描述符fd與plfs_file的映射關(guān)系。當(dāng)應(yīng)用通過文件描述符fd讀寫文件時,libplfs利用plfs_file文件對象直接調(diào)用PLFS API,同時將應(yīng)用傳遞過來的內(nèi)存地址、請求大小和文件偏移量傳遞給PLFS API;在PLFS上完成I/O請求后對虛擬文件描述符fd的當(dāng)前偏移量進行對應(yīng)的調(diào)整。在關(guān)閉文件時,釋放包括映射表中對應(yīng)項在內(nèi)的所有內(nèi)存資源。對于并發(fā)的I/O請求,例如若干個線程同時打開同一個文件,通過引用計數(shù)的方式記錄打開的次數(shù),避免創(chuàng)建冗余的對象,提高內(nèi)存利用率。
在進行I/O操作之前,libplfs對接收的參數(shù)進行判斷,區(qū)分該操作是一般的I/O操作還是在PLFS上相關(guān)的I/O操作。如果是對PLFS的操作,那么libplfs調(diào)用對應(yīng)PLFS API處理該請求,否則通過調(diào)用dlsym()函數(shù)得到libc中對應(yīng)I/O函數(shù)的指針并調(diào)用它,如偽代碼1中的第3和第5行。
libplfs重載了69個幾乎所有l(wèi)ibc中的I/O庫函數(shù)。用戶可以簡單地通過設(shè)置LD_PRELOAD環(huán)境變量,將目標(biāo)程序中的I/O操作重定向到libplfs動態(tài)鏈接庫中,進而直接調(diào)用PLFS API,達到繞過FUSE的目的。該方法對所有在運行時動態(tài)鏈接了libc庫中I/O相關(guān)函數(shù)的可執(zhí)行文件都適用。
測試集群由5個節(jié)點組成,其中1個作為主節(jié)點,4個以HDD(hard disk drive, 機械硬盤)為介質(zhì)的存儲節(jié)點。節(jié)點配置如表1所示。
表1 集群節(jié)點配置
PLFS 作為一個可堆疊文件系統(tǒng),實際不存儲數(shù)據(jù),而是將數(shù)據(jù)重新組織到底層文件系統(tǒng)中,向應(yīng)用提供一個虛擬文件的接口。CephFS[26]是常見的高性能的文件系統(tǒng),本文使用它作為PLFS的后端存儲。4個存儲節(jié)點組成一個Ceph對象存儲池,其中1個節(jié)點還充當(dāng)元數(shù)據(jù)服務(wù)器。主節(jié)點通過Ceph的內(nèi)核模塊將CephFS掛載到主節(jié)點的目錄樹中,將該掛載點目錄設(shè)置成PLFS的后端存儲。
測試libplfs時,在LD_PRELOAD的作用下,所有I/O函數(shù)的調(diào)用被重定向到libplfs中,該測試探究libplfs下PLFS的性能,記作no-FUSE。在有FUSE并且默認設(shè)置的情況下進行測試,作為控制組FUSE-with-cache。在有FUSE時,不使用內(nèi)核頁緩存的方式下進行測試,探究FUSE內(nèi)核模塊中頁緩存對性能的影響,記作FUSE-direct-IO。后端CephFS文件系統(tǒng)通過Ceph的內(nèi)核模塊掛載,并且啟用緩存功能,采用默認配置。
本文通過文件大小、傳輸塊大小和并發(fā)度3個參數(shù)的組合配置進行測試。每組實驗重復(fù)10次,實驗參數(shù)配置見表2。
表2 實驗參數(shù)設(shè)置
本文采用LANL開發(fā)的fs_test[27]作為基準(zhǔn)測試工具。為便于敘述,本文對測試點采用形如“4-512M-16K-read”的命名方式,表示4個線程并發(fā)讀同一個大小為512 MB的文件,傳輸塊大小為16 KB。在讀測試之前,我們先完成對應(yīng)的寫測試以此來達到系統(tǒng)預(yù)熱的效果,目的是更好地利用后端存儲的緩存,避免后端存儲成為性能瓶頸,從而掩蓋FUSE的性能影響,造成實驗數(shù)據(jù)偏差。每個測試點完成后,清空緩存中的數(shù)據(jù)。
得到測試結(jié)果之后,我們對一些測試點使用系統(tǒng)剖析工具進行深入分析,探究FUSE如何影響I/O性能,以及l(fā)ibplfs的優(yōu)勢。
本文選擇具有代表性的結(jié)果來進行說明,類別為小文件和大文件、串行和并發(fā)、讀和寫、小傳輸塊(1 KB, 4 KB, 16 KB)和大傳輸塊(64 KB, 256 KB, 1 MB)。
傳輸塊大小與緩存利用率直接相關(guān),決定了在給定文件大小條件下系統(tǒng)調(diào)用和內(nèi)核函數(shù)調(diào)用的數(shù)量。選擇傳輸塊大小作為x軸。每一個實驗運行10次,結(jié)果去掉最大最小值后取平均。
圖4展示讀測試的結(jié)果。從結(jié)果中可以得出,在所有傳輸塊大小下,no-FUSE總是比FUSE-direct-IO的帶寬要高。當(dāng)傳輸塊大于32 KB時,no-FUSE的帶寬最高,最大提升131%。當(dāng)傳輸塊小于32 KB時,F(xiàn)USE-with-cache帶寬最高。FUSE-with-cache的帶寬總是比FUSE-direct-IO高。變化趨勢方面,F(xiàn)USE-direct-IO與no-FUSE的變化趨勢類似,而FUSE-with-cache的變化趨勢比較平穩(wěn)。單線程與多線程下讀性能相仿。
圖4 讀性能比較
圖5展示寫測試的結(jié)果。從結(jié)果中可以得出,在所有傳輸塊大小下,F(xiàn)USE-direct-IO的帶寬總是比FUSE-with-cache高。當(dāng)傳輸塊小于64 KB時,no-FUSE帶寬最高,最大提升5.7倍。當(dāng)傳輸塊大于64 KB時,no-FUSE被FUSE-direct-IO超過,二者性能差距大部分位于1%~8%,但二者都比FUSE-with-cache高。
圖5 寫性能比較
圖6給出了寫測試中no-FUSE對比FUSE-with-cache所帶來的的性能提升結(jié)果。結(jié)果表明,當(dāng)傳輸塊為1 KB時,4線程寫測試的性能提升更大,當(dāng)傳輸塊大于1 KB時,單線程寫測試提升更加明顯。換句話說,在并發(fā)小請求的寫工作負荷下,繞過FUSE帶來的性能提升更加明顯。
圖6 寫性能提高比較
FUSE的內(nèi)核模塊可以利用內(nèi)核頁緩存,進行預(yù)取和寫回操作,進而影響性能。通過libplfs繞過FUSE之后,運行態(tài)切換、系統(tǒng)調(diào)用和內(nèi)存拷貝的減少有利于提升性能。
本節(jié)借助Linux系統(tǒng)提供的系統(tǒng)調(diào)優(yōu)和調(diào)試工具對得到的結(jié)果進行分析和研究,所涉及的工具包括perf和ftrace。我們利用這些工具統(tǒng)計分析I/O過程中系統(tǒng)調(diào)用操作的數(shù)量、內(nèi)核函數(shù)調(diào)用的情況以及頁緩存的使用率。
3.4.1 讀操作與頁緩存利用率
FUSE的主要特點之一是可以利用內(nèi)核頁緩存。Linux內(nèi)核將FUSE內(nèi)核模塊和底層文件系統(tǒng)當(dāng)作2個不同的文件系統(tǒng),它們在內(nèi)核有獨立的緩存空間,因此FUSE存在雙重緩存的問題。換句話說,對一個文件進行讀操作將導(dǎo)致同一份內(nèi)容被緩存2次。在CephFS緩存被清空后運行“4-2G-1K-read”測試,頁緩存使用情況見表3。
表3 清空緩存后測試緩存使用情況
表3結(jié)果顯示,當(dāng)后端存儲緩存被清空時,雙重緩存使FUSE-with-cache的緩存命中率最低;當(dāng)后端緩存被預(yù)先填充時,F(xiàn)USE-with-cache的緩存命中率可以提高到80%左右。
在讀測試之前,先進行寫測試然后卸載PLFS,保證讀測試時CephFS的緩存處于有效狀態(tài)。重新運行“4-128M-1K-read”和“4-128M-1M-read”測試項,統(tǒng)計VFS、FUSE和CephFS上內(nèi)核函數(shù)的調(diào)用情況,結(jié)果見表4。
表4 內(nèi)核函數(shù)調(diào)用數(shù)量
當(dāng)傳輸塊為1 KB時,F(xiàn)USE-with-cache在緩存的作用下,使Ceph上的讀操作只被觸發(fā)1 506次;FUSE-direct-IO和no-FUSE的情況下,Ceph上的讀操作是其350倍之高。當(dāng)傳輸塊為1 MB時,它們之間的差距縮小很多,只有2.4和1.1倍。結(jié)果表明,當(dāng)傳輸塊比較小時,在內(nèi)核頁緩存的作用下,F(xiàn)USE-with-cache的性能最高。
3.4.2 系統(tǒng)調(diào)用(運行態(tài)切換)
I/O通過系統(tǒng)調(diào)用完成,系統(tǒng)調(diào)用數(shù)量與運行態(tài)切換數(shù)量正相關(guān)。當(dāng)傳輸塊很小時,一個I/O請求的實際數(shù)據(jù)傳輸時間很短,導(dǎo)致運行態(tài)切換開銷占比很大。除此之外,系統(tǒng)調(diào)用數(shù)量與用戶空間和內(nèi)核空間之間的內(nèi)存拷貝次數(shù)存在密切聯(lián)系。對“4-128M-1K-write”和“4-128M-1M-read”兩項測試中觸發(fā)的系統(tǒng)調(diào)用次數(shù)進行統(tǒng)計,得到表5。
表5 系統(tǒng)調(diào)用情況
表5展示了測試的帶寬和系統(tǒng)調(diào)用數(shù)量。在傳輸塊為1 KB的寫測試中,F(xiàn)USE-direct-IO的系統(tǒng)調(diào)用數(shù)量是no-FUSE的4.4倍,然而每一個I/O請求的數(shù)據(jù)傳輸時間很短,大量系統(tǒng)調(diào)用使運行態(tài)切換的總開銷占比很高,導(dǎo)致no-FUSE的寫性能是FUSE-direct-IO的3.5倍。對于傳輸塊為1 MB的讀測試,no-FUSE的系統(tǒng)調(diào)用數(shù)量為34 267遠小于FUSE-direct-IO的91 872,導(dǎo)致no-FUSE的讀性能提升了131%。
本文研究FUSE在并行文件系統(tǒng)中的性能問題,基于動態(tài)鏈接的機制,實現(xiàn)了一種繞過FUSE的libplfs動態(tài)鏈接庫,并且在PLFS文件系統(tǒng)進行實驗和驗證。實驗結(jié)果表明,通過libplfs繞過FUSE后,可以在保證寫性能的前提下,當(dāng)傳輸塊較大時,讀性能提高最大可達131%;當(dāng)傳輸塊較小時,寫性能提高最大5倍左右。對結(jié)果進行研究分析,發(fā)現(xiàn)內(nèi)核頁緩存和大量的系統(tǒng)調(diào)用是FUSE影響性能的重要因素?;贔USE的文件系統(tǒng)在傳輸塊較小時,雙重緩存給文件系統(tǒng)的讀性能帶來了一定的性能提升,但同時大量的系統(tǒng)調(diào)用使其寫性能降低2.5倍以上。對于并行文件系統(tǒng),并且存在大量大塊I/O,利用libplfs的方法可以帶顯著的性能提升。我們后續(xù)將對基于FUSE的不同文件系統(tǒng)進行研究,開展關(guān)于FUSE系統(tǒng)性能優(yōu)化的通用性方面的探索,擴展現(xiàn)有的優(yōu)化方案。
中國科學(xué)院大學(xué)學(xué)報2022年2期