級別: 中級 Arpan Sen, 首席工程師, Systems Documentation, Inc. (SDI)
2009 年 2 月 09 日 通過本文,您將了解能夠通過將編譯過程分布到本地網(wǎng)絡(luò)中的多臺機器上,從而加快速度的開源工具選項。 減少基于 C/C++ 的系統(tǒng)的編譯時間是所有發(fā)布和編譯工程師所面對的主要挑戰(zhàn)之一。本文研究一些可通過并行活動來加快編譯過程的開源工具選項:將編譯過程分布到本地網(wǎng)絡(luò)中的多臺機器上。本文中的討論主要集中于 GNU make ,因為它使用比較廣泛。 GNU make 中的 –j 選項 默認情況下,make 是一個順序工作的工具。它按次序調(diào)用底層編譯器來編譯 C/C++ 源。通常,C/C++ 源文件(通常帶有 .cpp/.cxx 擴展名)不需以對方為基礎(chǔ)即可編譯。使用 –j 選項調(diào)用 make 來完成該操作。清單 1 顯示的是一種典型的用法。 清單 1. 典型的 GNU make 調(diào)用 make –j10 –f makefile.x86_linux | –j -- 10 的參數(shù)是編譯過程開始后能同時進行的最大編譯數(shù)。如果沒有給 -j 提供任何參數(shù),則所有源文件都會在系統(tǒng)中排隊,等待同時編譯。在運行多核系統(tǒng)上的編譯時,使用 -j 選項特別有用。要使用 -j 選項,必須先解決幾個關(guān)鍵問題;這些問題將在下面部分討論。 使用 –j 選項時的問題和可能的解決方案 首先要檢查系統(tǒng)配置。在低內(nèi)存(<512MB RAM)系統(tǒng)上,同時編譯的數(shù)量太多會因為分頁而使系統(tǒng)變慢。在這種情況下會增加編譯時間。您需要進行試驗以得出系統(tǒng)的最佳 -j 值。另一種選擇是使用 GNU make 工具的 –l 或 –load-average 選項,同時也使用 -j (它只在系統(tǒng)負載小于一定水平時才觸發(fā)作業(yè))。 還可以使用同一個臨時文件進行獨立編譯。請考慮清單 2 中所示的 make 代碼片段。 清單 2. 使用同一個臨時文件 y.tab.c 的 makefile。 my_parser : main.o parser1.o parser2.o g++ -o $* $> parser1.o : parser1.y yacc parser1.y g++ -o $* -c y.tab.c parser2.o : parser2.y yacc parser2.y g++ -o $* -c y.tab.c | 假設(shè)語法文件 parser1.y 和 parser2.y 位于同一目錄中。在有序編譯期間,yacc(其中 y.tab.c 是默認文件名)為 parserl 生成文件 y.tab.c,然后為 parser2 生成文件 y.tab.c;但在并行模式下,這會導致沖突。有幾種方法可以解決這個問題:將兩個 yacc 文件放在單獨的文件夾中;或者使用 –b 選項生成兩個不同的 C 輸出,如清單 3 所示。 清單 3. 使用 yacc 的 –b 選項生成唯一的文件名 parser1.o : parser1.y yacc parser1.y –b parser1 g++ -o $* -c parser1.tab.c | 您必須嚴密監(jiān)視 makefile 是否發(fā)生這種情況,在這種情況下,在順序模式下良好編譯的腳本會在并行模式下出現(xiàn)混亂。 一些 makefile 規(guī)則具有隱式依賴項。請考慮清單 4 中的情況,其中一個 Perl 腳本生成一個被其他源包含的頭。 清單 4. 具有隱式依賴項 makefile my_exe: info.h test1.o test2.o g++ -o $@ $^ test1.o: test1.cxx g++ -c $< test2.o: test2.cxx g++ -c $< info.h: make_header #shell script that generates the header file | info.h 頭被 test1.cxx 和 test2.cxx 包含。在次序編譯模式下,make 從左到右工作,首先生成文件 info.h。但是,在并行編譯模式下,make 可以自由并行處理所有依賴項 ——如果 info.h 沒有在 test1.cxx 和/或 test2.cxx 編譯開始之前生成的話,這可能導致間歇性編譯失敗。要修復此問題,需要將 info.h 從依賴項列表中刪除,并將它放在 test1.o 和 test2.o 的依賴項列表中。另外,最好使用另一個包裝器來確保 info.h 只生成一次。清單 5 顯示了修改后的 make_header 腳本,而清單 6 顯示了 makefile。 清單 5. 修改 make_header 腳本防止多次編寫 #!/usr/bin/bash if [ -f info.h ] then exit fi echo "#ifndef __INFO_H" > info.h echo "#define __INFO_H" > > info.h echo "#include <iostream>>" > > info.h echo "using namespace std;" > > info.h echo "int f1(int);" > > info.h echo "int f2(int);" > > info.h echo "#endif" > > info.h |
清單 6. 修改后的清單 4 中的 makefile my_exe: info.h test1.o test2.o g++ -o $@ $^ test1.o: test1.cxx info.h g++ -c $< test2.o: test2.cxx info.h g++ -c $< info.h: make_header #shell script that generates the header file | 一般而言,如果正確創(chuàng)建 makefile,make -j 就能夠提取充足的并行項。盡量避免在 makefile 中引入不必要的依賴項。 注意,GNU make 只能提取單臺機器的并行項。下一部分將介紹 distcc ,這是一個用于在多臺機器上共享編譯過程的工具。 distcc 簡介 distcc 工具可以將 C/C++ 代碼的編譯分布到多臺機器。但這些機器都必須安裝 distcc 。下面是關(guān)于快速安裝和配置的說明: - 下載
distcc (請參閱 參考資料 部分)。 - 通過執(zhí)行
./configure; make && make install 在所有機器上編譯 distcc 源。 - 編譯過程先在某臺機器上開始,然后分布所有其他機器(服務(wù)器)。在所有服務(wù)器中,啟動 distccd 守護程序(您必須具有執(zhí)行操作的根特權(quán))。distccd 位于 /etc/init.d 文件夾。在根模式下啟動 distccd 的語法是
tcsh-arpan# /etc/init.d/distccd start | 在用戶模式下啟動它的語法是 tcsh-arpan$ sudo /etc/init.d/distccd | 還可以通過運行 distccd –daemon –j N 在用戶模式下運行 distcc 守護進程,其中 N 是您要在給定機器上運行的作業(yè)數(shù)。 - 本地機器需要知道應該將編譯過程分布到哪些服務(wù)器。根據(jù)您的 shell,發(fā)出與下面命令相似的命令:
export DISTCC_HOSTS='localhost tintin asterix pogo' | tintin、asterix 和 pogo 是網(wǎng)絡(luò)中可以駐留編譯過程的其他主機;localhost 是本地計算機。 - 也可以不使用導出指令。您可以創(chuàng)建一個名為 hosts 的文件,將服務(wù)器的名稱放在該文件中,各個名稱使用空格分隔。將該文件復制到 $HOME/.
distcc 文件夾。 | distcc 的工作原理
distcc 將預處理代碼發(fā)送給網(wǎng)絡(luò)中的其他指定機器。distccd 守護進程確保編譯在遠程機器上發(fā)生。distcc 的設(shè)計目的是與 GNU make 的并行編譯(-j )選項一起使用。distcc 本身不是一個編譯器;它只是用作 g++ 的一個前端。幾乎 g++ 的所有選項都可以按原樣傳遞給 distcc 。 | | 安裝 distcc 之后,惟一需要做的就是觸發(fā)編譯。下面是調(diào)用方法: make –j4 CC=distcc –f makefile.x86_linux | 使用 distcc 需要記住的幾個關(guān)鍵點 要使 distcc 為您工作,必須記住以下幾件事情: - 幾臺機器必須具有一致的配置。這意味著所有機器上必須安裝相同版本的 g++ 編譯器,以及相關(guān)的編譯工具,如 ar、ranlib、libtool 等。操作系統(tǒng)的類型和版本也應該相同。
- 在客戶端機器上,
distcc 將預處理代碼發(fā)送給服務(wù)器機器。您需要驗證 distccd 守護進程正在服務(wù)器機器上運行。 - 默認情況下,
distcc 在單臺機器上調(diào)度的作業(yè)數(shù)是 CPU 的個數(shù) + 2。對于單核機器,這個數(shù)是 3。在觸發(fā)進程時請記住這一點:像 make –j10 CC=distcc 這樣的命令行(其中只有三個主機)意味著最初觸發(fā) 9 個編譯作業(yè)。 - 保證底層機器可以訪問存儲源文件的必備文件系統(tǒng)。在基于網(wǎng)絡(luò)文件系統(tǒng)(Network File System,NFS)的系統(tǒng)中,一些源區(qū)域不能被掛載,這將導致編譯失敗。同時還要仔細監(jiān)視網(wǎng)絡(luò)堵塞。
distcc 用于通過網(wǎng)絡(luò)編譯源代碼。鏈接步驟可能不是并行的。 | 必須按次序運行的編譯過程 編譯中的有些步驟可能不是并行的 —— 必須在單臺機器上才能使用腳本生成某些頭、鏈接等。要更好地處理這種情況,最好將原始 makefile 拆分為多個 makefile,明確劃分哪些可以并行運行,哪些不可以,然后按以下方式運行它們: tcsh-arpan$ make –f make.init; make CC=distcc –j4 –f make.compile_x86; make –f make.link |
| | 監(jiān)視 distcc 編譯過程 distcc 安裝有一個稱為 distccmon-text 的基于控制臺的監(jiān)視工具。在啟動編譯過程之前,有必要打開一個單獨的終端窗口并發(fā)出 distccmon-text 5。然后,這個終端每隔 5 秒鐘就顯示網(wǎng)絡(luò)中多個節(jié)點的編譯狀態(tài)。清單 7 顯示了一個監(jiān)視窗口示例。 清單7:distccmon-text 的輸出 2167 Compile memory.c tintin[0] 2164 Compile main.cxx tintin[1] 2192 Compile ui_tcl.cxx asterix[0] 2187 Compile traverse.c asterix[1] 2177 Compile reports.cxx pogo[0] 2184 Compile messghandler.c pogo[1] 2181 Compile trace.cpp localhost[0] 2189 Compile remote.c localhost[1] | 使用 ccache 進一步提高編譯速度 通常,當在 C/C++ 開發(fā)框架中修改頭文件時,一般基于 make 的系統(tǒng)最終會重新編譯所有源文件。通常,頭文件更改只會影響源文件的子集,因此不需要進行耗時的編譯清理。還可以使用 ccache ,這個工具能大大減少編譯清理時間(減少到原來的五分之一至十分之一)。 ccache 用作編譯器的緩存。它的工作方式是:從預處理源代碼和用于編譯源代碼的編譯器選項創(chuàng)建一個哈希表。在重新編譯時,如果 ccache 未在預處理源代碼和編譯器選項中檢測到任何更改,它就檢索以前編譯輸出的緩存副本。這有助于加快編譯過程。 安裝 ccache 要下載 ccache 的最新版本(2.4),請參考 參考資料 小節(jié)。轉(zhuǎn)到 ccache 目錄后,發(fā)出命令 ./configure –prefix=/usr/bin ,接著發(fā)出命令 make && make install 。如果 ccache 沒有安裝在 /usr/bin,則檢查 ccache 的位置是否定義為 PATH 環(huán)境變量的一部分。 Ccache 環(huán)境變量 下面是一些可用于自定義 ccache 安裝的環(huán)境變量: CCACHE_DIR —— 指定 ccache 存儲預編譯輸出的文件夾。如果沒有定義這個變量,那么緩存輸出會默認存儲在 $HOME/.ccache 中。 CCACHE_TEMPDIR —— 指定放置 ccache 生成的臨時文件的文件夾。如果沒有定義這個變量,那么默認使用 $HOME/.ccache。最好定義這個變量和 CCACHE_DIR —— 大多數(shù)組織有一個針對特定文件系統(tǒng)區(qū)域的用戶配額,如果 $HOME 屬于這個區(qū)域,那么配額很快就會用完。顯式地設(shè)置這個緩存區(qū)域以避免此類問題。 CCACHE_DISABLE —— 設(shè)置這個選項告訴 ccache 完全調(diào)用編譯器,從而繞過緩存。這在診斷時使用。 CCACHE_RECACHE —— 設(shè)置這個選項告訴 ccache 忽略緩存中現(xiàn)有的條目并調(diào)用編譯器;但對于新的條目,則緩存結(jié)果。這在診斷時使用。 CCACHE_LOGFILE —— 設(shè)置這個選項告訴 ccache 隨機記錄該文件在緩存中的統(tǒng)計信息。這對診斷特別有用。 CCACHE_PREFIX —— 向 ccache 用于完全調(diào)用編譯器的命令行添加一個前綴。這專門用于將 distcc 和 ccache 連接起來。下一部分將會對此進行詳細討論。 使用 ccache 使用 ccache 時,可以帶有 distcc ,也可以不帶。這不依賴于 -j makefile 選項。ccache 最簡單的用法如下:ccache g++ -o <executable name> <source file(s)> 。當它與 makefile 一起使用時,就會覆蓋 CC 變量;如清單 8 所示。 清單 8. 使用 CC 變量的示例 makefile CC := g++ app1: placer1.o route1.o floorplan1.o $(CC) –o $* $^ placer1.o: placer1.cxx $(CC) –o $* -c $< … | 使用清單 8 中的 makefile,發(fā)出 make 的語法是 make "CC=ccache g++" 。 為了同時使用 ccache 和 distcc ,需要將 CCACHE_PREFIX 環(huán)境變量設(shè)置為 distcc ,如下所示:export CCACHE_PREFIX=distcc (這個語法適用于 bash shell。如果使用另一種 shell,請相應地修改語法)。 下面是一個使用 ccache 和 distcc 的 make 調(diào)用示例: export CCACHE_PREFIX=distcc; make "CC=ccache g++" –j4 –f makefile.x86 | 在編譯過程中,shell 提示符下的實際調(diào)用類似于:ccache distcc –o placer1.o –c placer1.cxx 。注意,只需在本地機器上安裝 ccache 。ccache 進行第一次檢查,確定副本是否存在本地緩存中;如果不存在,就由 distcc 進行分布式編譯。 結(jié)束語 本文探討了 GNU make 、distcc 和 ccache ,這些工具能夠并行分布編譯過程。它們還有幾個可以進一步自定義的其他特性 —— 例如,ccache 有一個限制緩存大小的 –M 選項;distcc 有一個基于 GUI 的監(jiān)視器 distcc -gnome,它會跟蹤網(wǎng)絡(luò)編譯活動(如果使用 –use-gtk 選項編譯 distcc ,就會創(chuàng)建該監(jiān)視器)。參考資料 部分中的鏈接提供更加詳細的信息。 |