本文標(biāo)題中的“分布式編譯”是一種通過(guò)在局域網(wǎng)內(nèi)的多個(gè)節(jié)點(diǎn)上運(yùn)行編譯進(jìn)程來(lái)提高構(gòu)建速度的途徑。然而在我們的實(shí)踐當(dāng)中發(fā)現(xiàn),單純的向各個(gè)節(jié)點(diǎn)分發(fā)任務(wù)而不考慮負(fù)載均衡往往會(huì)產(chǎn)生性能上的瓶頸。本文主要介紹如何解決這個(gè)問(wèn)題。
使用 distcc 分布式編譯的特點(diǎn)與潛在問(wèn)題
作為經(jīng)典的分布式編譯工具,distcc 在日常工作中常為我們使用來(lái)解決大型項(xiàng)目在單一工作站上編譯較慢的問(wèn)題。其主要用于對(duì) C, Object C 以及 C++ 代碼進(jìn)行并行編譯,將可以并行的編譯任務(wù)分布于編譯集群中的各個(gè)工作站,有效利用各機(jī)器資源,達(dá)到整體編譯性能的成倍提升。
在類(lèi) Unix 系統(tǒng)上,distcc 使用 sendfile 系統(tǒng)調(diào)用在不同工作節(jié)點(diǎn)之間傳送文件,盡管這種網(wǎng)絡(luò)文件傳輸會(huì)占用一定的時(shí)間,他們對(duì)工作機(jī)的 CPU 資源占用卻很小,而且這種分發(fā)任務(wù)的方式能夠簡(jiǎn)化構(gòu)建環(huán)境的配置,distcc 在這方面同早期的一些基于共享文件系統(tǒng)的分布編譯環(huán)境 (dmake, pvmmake 等等 ) 相比幾乎是 0 配置。
distcc 對(duì)各個(gè)編譯節(jié)點(diǎn)的本地系統(tǒng)庫(kù)及頭文件基本沒(méi)有要求,即使在不同的節(jié)點(diǎn)上這些組件的版本不同也不會(huì)影響到最終編譯結(jié)果的正確性,實(shí)際情況是 distcc 會(huì)在本地 (client 機(jī) ) 完成存在版本依賴的編譯任務(wù),這一點(diǎn)的實(shí)現(xiàn)原理簡(jiǎn)要說(shuō)來(lái)式因?yàn)?distcc 借助了 C/C++ 編譯驅(qū)動(dòng)程序的以下特點(diǎn):
- cpp(C 預(yù)處理器 ) [cpp [arguments] .c 源文件輸入 .i 中間文件輸出 ]
- ccl(C 編譯器 ) [ccl .i 中間文件輸入 .c 源文件輸入 [arguments] –o .s 匯編文件輸出 ]
- as( 匯編器 ) [as [arguments] –o .o 目標(biāo)文件輸出 .s 匯編文件輸入 ]
這個(gè)在本地做過(guò)預(yù)處理的 ASCII 源文件及其他命令行選項(xiàng)即可唯一確定一個(gè)目標(biāo)文件,而與此任務(wù)在哪臺(tái)機(jī)器上運(yùn)行無(wú)關(guān),通過(guò)分發(fā)這種任務(wù)到各個(gè)節(jié)點(diǎn),即可消除對(duì)頭文件的依賴。同理 distcc 通過(guò)在任務(wù)的分發(fā)節(jié)點(diǎn)做鏈接來(lái)消除對(duì)庫(kù)文件的依賴。
然而,distcc 的缺點(diǎn)在于其負(fù)載均衡算法過(guò)于簡(jiǎn)單,distcc 的代理進(jìn)程對(duì)各個(gè)工作機(jī)當(dāng)前的負(fù)荷沒(méi)有感知,分發(fā)預(yù)處理文件的唯一依據(jù)是主機(jī)出現(xiàn)在 DISTCC_HOST 環(huán)境變量中的次序,主機(jī)名越靠前,就會(huì)得到更多的編譯任務(wù),然而當(dāng)編譯場(chǎng)中某些機(jī)器性能過(guò)差,整體編譯性能會(huì)顯著下降,當(dāng)阻塞 Make 運(yùn)行的編譯任務(wù)運(yùn)行在這些機(jī)器上的時(shí)候,這種性能變化尤為明顯。
假設(shè)這樣一種極端的情況:集群中兩臺(tái)工作機(jī)中一臺(tái)使用 4 個(gè)強(qiáng)勁的 CPU,而另一臺(tái)性能較弱的機(jī)器只有 2 個(gè)較弱的 CPU。如果使用 distcc 啟動(dòng)并行數(shù)量為 6 的編譯任務(wù),這將導(dǎo)致較弱的 2 CPU 工作機(jī)負(fù)載過(guò)載而較強(qiáng)的 4CPU 工作機(jī)負(fù)載過(guò)輕,這樣的非均勻負(fù)載分布將拖累整個(gè)編譯任務(wù)。如圖所示:
圖 1:無(wú)負(fù)載均衡的并行編譯任務(wù)演示

一種實(shí)現(xiàn)負(fù)載均衡的解決方案:使用 DMUCS
毫無(wú)疑問(wèn),在上述編譯集群中,有必要采用負(fù)載均衡來(lái)使編譯系能得到最大的優(yōu)化。這就需要在編譯集群中增加監(jiān)控各工作機(jī)工作量的監(jiān)控程序,動(dòng)態(tài)檢測(cè)和平衡編譯機(jī)的負(fù)載。
一個(gè)有效的方案是使用 DMUCS(Distributed Multi-User Compilation System)應(yīng)用。DMUCS 是一個(gè)實(shí)現(xiàn)于 distcc 之上的動(dòng)態(tài)平衡和任務(wù)分布程序。它可以:
- 支持多用戶同時(shí)編譯,擴(kuò)展性好,可以很好處理新增的負(fù)載。
- 支持多種操作系統(tǒng)所組成的編譯集群。
- 可以使用具有多處理器(多核)編譯主機(jī)的所有處理資源。
- 可以充分使用具有不同處理速度的編譯主機(jī),使整體編譯性能達(dá)到最優(yōu)。
- 可以保證參與編譯的主機(jī)不會(huì)由于編譯任務(wù)而產(chǎn)生超負(fù)載的情況。
- 考慮到了編譯主機(jī)上由非編譯任務(wù)所引起的負(fù)載情況。
- 支持從編譯集群中動(dòng)態(tài)的增加或者移除編譯主機(jī)。
DMUCS 以下四個(gè)部分程序組成。
DMUCS 解決方案的核心服務(wù)程序。每個(gè)編譯集群僅運(yùn)行一個(gè) dmucs 主服務(wù)程序,其運(yùn)行于哪一臺(tái)主機(jī)上沒(méi)有限制。該程序從一個(gè)配置文件讀出編譯場(chǎng)里處理器數(shù)目和每個(gè)可能主機(jī)的“潛能”。然后從網(wǎng)絡(luò)接收每個(gè)編譯主機(jī)的平均負(fù)載信息,編譯任務(wù)數(shù)量和監(jiān)控程序得到的編譯請(qǐng)求信息。dmucs 管理一個(gè)編譯場(chǎng)里主機(jī)的數(shù)據(jù)庫(kù),并調(diào)度主機(jī)去編譯任務(wù),當(dāng)有編譯請(qǐng)求時(shí)給出可用的最快的主機(jī) / 處理器。
編譯場(chǎng)的每個(gè)參與編譯的主機(jī)上均需要啟動(dòng)這個(gè)程序。loadavg 定期發(fā)送編譯主機(jī)的平均負(fù)載到 dmucs 服務(wù)器。這樣當(dāng)某個(gè)主機(jī)的平均負(fù)載太高時(shí),dmucs 服務(wù)器會(huì)將不會(huì)優(yōu)先給該主機(jī)分配編譯任務(wù)。
gethost 是具體進(jìn)行編譯的命令,其運(yùn)行于 distcc 之上。該命令從運(yùn)行 dmucs 主服務(wù)程序的主機(jī)獲取編譯集群中的機(jī)器信息來(lái)獲得放進(jìn) DISTCC_HOSTS 環(huán)境變量里的主機(jī),然后調(diào)用所分配的編譯機(jī)進(jìn)行編譯。在編譯結(jié)束后,gethost 釋放所分配的主機(jī)到 dmucs 主服務(wù)器。用戶使用“make CXX=gethost distcc”來(lái)啟動(dòng)編譯。
編譯集群的管理員可以使用這個(gè)程序監(jiān)控編譯場(chǎng)的繁忙情況。
在上例中,采用 DMUCS 的負(fù)載均衡來(lái)配置編譯集群,當(dāng)其啟動(dòng)并行數(shù)量為 6 的編譯任務(wù)時(shí),理想狀態(tài)下 DMUCS 會(huì)首先將任務(wù)優(yōu)先分布到高性能的編譯機(jī)進(jìn)行編譯。當(dāng)高性能編譯機(jī)上的負(fù)載飽滿以后,余下的編譯任務(wù)將被分布到低性能的編譯機(jī)上進(jìn)行編譯。由此,每個(gè)編譯機(jī)資源將得到最大限度的利用,編譯性能得到最大提升。
圖 2:帶負(fù)載均衡的并行編譯任務(wù)演示

部署負(fù)載均衡的分布式編譯環(huán)境
本節(jié)介紹如何架設(shè) / 配置你的分布式編譯環(huán)境來(lái)獲得最好的性能,包括如下幾點(diǎn)提示:
- 使用 distcc 為你的工作節(jié)點(diǎn)指定角色;
- 在此基礎(chǔ)上配置 DMUCS 來(lái)實(shí)現(xiàn)性能優(yōu)化;
本節(jié)中我們將主要討論 distcc 和 DMUCS 的特性和配置,并針對(duì)上述兩點(diǎn)做詳細(xì)討論。
distcc 的安裝與配置
distcc 的基本安裝,首先下載源代碼到任意目錄解壓后依次運(yùn)行
./configure, make,make install
即可完成安裝,如果同時(shí)需要安裝圖形化任務(wù)監(jiān)控前端,請(qǐng)根據(jù)主機(jī)圖形庫(kù)支持情況指定—with-gnome 或—with-gtk. distcc 程序組的默認(rèn)安裝位置是 /usr/local/bin,如果需要安裝到其余位置,請(qǐng)?jiān)谂渲镁幾g時(shí)修改 configure 的各個(gè)路徑變量。
distcc 會(huì)安裝如下可執(zhí)行文件:
- distcc: 整個(gè)編譯任務(wù)通常由一臺(tái)機(jī)器發(fā)起,在 distcc 編譯環(huán)境下,這臺(tái)機(jī)器被稱(chēng)為 client,client 必須使用 distcc 來(lái)替代原有的 GNU 編譯器命令,由于 distcc 的后臺(tái)編輯程序仍然是 GNU 編譯器,distcc 與 gcc, g++, cc, c++ 等程序的編譯參數(shù)兼容。distcc 必須與 Make 命令的 -j 參數(shù)協(xié)同使用,client 機(jī)通過(guò)指定此參數(shù)來(lái)定義并發(fā)編譯的任務(wù)數(shù)。在默認(rèn)情況下,一臺(tái)編譯機(jī)的 distcc 允許的并發(fā)任務(wù)的數(shù)量是 CPU 數(shù)量 +2。
- distccd: distccd 是運(yùn)行在編譯場(chǎng)內(nèi)各個(gè)節(jié)點(diǎn)上的 distcc 代理程序,distccd 的常用參數(shù)如下:
- -j: 指定可以在本節(jié)點(diǎn)上運(yùn)行的最大任務(wù)數(shù);
- -N: 如果編譯節(jié)點(diǎn)上運(yùn)行有其他重要任務(wù),可以通過(guò)指定 -N 參數(shù)來(lái)調(diào)整編譯進(jìn)程的運(yùn)行優(yōu)先級(jí);
- -a: 指定 distccd 可以接受來(lái)自哪些節(jié)點(diǎn)的連接請(qǐng)求,-a 參數(shù)的值可以是一個(gè)網(wǎng)段,也可以是所有編輯節(jié)點(diǎn)主機(jī)名 (IP 地址 ) 的列表。
- distccmon-text: 可以通過(guò)運(yùn)行 distccmon-text 來(lái)通過(guò)一個(gè)字符界面監(jiān)控整個(gè)編譯任務(wù),此命令唯一的參數(shù)是監(jiān)控任務(wù)的刷新間隔 ( 秒 )。
- distccmon-gnome: 是一個(gè)圖形化的監(jiān)控前端,下圖是此程序的一個(gè)運(yùn)行實(shí)例。其中,任務(wù)進(jìn)度指示條顏色的意義分別為:綠色:compiling;紫色:preprocessing;藍(lán)色:receiving;橙色:connecting;白色:idle;
圖 3:distccmon-gnome: 監(jiān)控前端

Distcc client 通過(guò)配置環(huán)境變量 DISTCC_HOSTS 來(lái)指定編譯場(chǎng)中的各個(gè)節(jié)點(diǎn),具體命令如::
export DISTCC_HOSTS="192.168.1.1, 192.168.1.2"
|
此處需注意在此環(huán)境變量中位置靠前的主機(jī)會(huì)得到更多的編譯任務(wù),distcc 工具通過(guò)這種機(jī)制來(lái)實(shí)現(xiàn)簡(jiǎn)單的負(fù)載均衡,雖然我們認(rèn)為這種機(jī)制是不完備的。
DMUCS 的安裝與配置
從 http://prdownloads.sourceforge.net/dmucs/dmucs-0.6.tar.bz2?download 下載 dmucs 的最新源碼包。Dmucs 的安裝使用的是標(biāo)準(zhǔn)的 configure,make,make install 的方式。
首先在解壓開(kāi)的源碼包的根目錄下運(yùn)行 ./configure,默認(rèn)是設(shè)置 /usr/local 為安裝目錄,你也可以使用—prefix 參數(shù)來(lái)指定其他的安裝目錄,這同時(shí)也改變了 dmucs 啟動(dòng)時(shí)所需要的 hosts-info 文件(下文會(huì)詳細(xì)介紹該文件的內(nèi)容)所在的路徑。默認(rèn)情況下這個(gè)文件的所在路徑是 /usr/local/share/dmucs/hosts-info。
第二步就是執(zhí)行 make 命令來(lái)編譯 dmucs,這里要注意的是需要使用一個(gè) make 選項(xiàng) CPPFLAGS=-DSERVER_MACH_NAME=\\\”<DSERVER-IP>\\\”,這里的 <DSERVER-IP> 是你選擇運(yùn)行 dmucs 服務(wù)器的 IP 地址。
第三步是運(yùn)行 make install 將編譯好的可執(zhí)行文件安裝到指定的安裝目錄下。
以上的安裝步驟在每臺(tái)機(jī)器上(包括 dmucs 主機(jī)和實(shí)際執(zhí)行編譯任務(wù)的 host 主機(jī))都要執(zhí)行一遍。
在運(yùn)行 dmucs 之前要確保完成以下配置:
- 確保每個(gè)實(shí)際參與編譯任務(wù)的主機(jī)都安裝了 gcc 編譯器和 distcc(至少需要 distcc 和 distccd)。
- 確保 loadavg 在每個(gè)參與編譯任務(wù)的主機(jī)上都可以被執(zhí)行。
- 確保 dmucs 程序在被你選作 dmucs sever 的主機(jī)上可以被執(zhí)行。
- 確保 hosts-info 文件已被創(chuàng)建。該文件的默認(rèn)路徑是 /usr/local/share/dmucs/hosts-info,如果你在之前的安裝步驟中指定過(guò)其他的安裝目錄(使用過(guò)—prefix 參數(shù))那么這個(gè)文件所在路徑是 <prefix>/share/dmucs/hosts-info。這里簡(jiǎn)單介紹一下這個(gè)文件的格式:
清單 1:hosts-info 配置
#Format:host-name number-of-cpus power-index
9.123.247.65 2 3
9.123.245.91 2 3
9.47.98.59 1 1
9.47.98.90 8 15
|
以上文件格式比較簡(jiǎn)單,每一行代表一臺(tái)實(shí)際參與編譯的主機(jī)的信息:主機(jī)名或 IP 地址,cpu(或者核)的個(gè)數(shù)和“性能指數(shù)。前兩個(gè)屬性的含義都是一目了然的,第三個(gè)屬性“性能指數(shù)”是個(gè)相對(duì)數(shù)值(大于等于 1),如果你確定一臺(tái)主機(jī)的性能強(qiáng)過(guò)另一臺(tái)主機(jī),這臺(tái)主機(jī)的“性能指數(shù)就要相對(duì)大些。一種確定“性能指數(shù)”的方式是,讓幾臺(tái)主機(jī)都單獨(dú)執(zhí)行一次同樣的編譯任務(wù),分別記錄所需的時(shí)間。然后指定所花時(shí)間最長(zhǎng)的那臺(tái)主機(jī)的性能參數(shù)(比如說(shuō) 2),那么其他主機(jī)的性能指數(shù)的設(shè)定按照其所花編譯時(shí)間與最慢的那臺(tái)主機(jī)所花時(shí)間的比例來(lái)設(shè)置。比如說(shuō),如果有一臺(tái)主機(jī)只花了最慢主機(jī)一半的時(shí)間,那么它的性能指數(shù)就應(yīng)被設(shè)置為 4。其他的主機(jī)以此類(lèi)推。
完整啟動(dòng) DMUCS 程序由以下步驟組成:
- 在 dmucs server 上啟動(dòng) dmucs 程序。
- 在每臺(tái)實(shí)際參與編譯的主機(jī)上運(yùn)行 distccd 守護(hù)進(jìn)程。
- 在每臺(tái)實(shí)際參與編譯的主機(jī)上執(zhí)行 loadavg 程序,該程序默認(rèn)向編譯時(shí)所指定的 <DSERVER-IP> 的 dmucs server 發(fā)送該臺(tái)主機(jī)的 IP 地址和負(fù)載信息。你也可以通過(guò) -s 參數(shù)指定 dmucs server 的地址信息。
這里有個(gè)讀者特別需要注意的地方,loadavg 程序是先獲得主機(jī)的 hostname,之后再通過(guò) hostname 去獲取主機(jī) IP 的。這就有個(gè)問(wèn)題就是,假設(shè)某臺(tái)主機(jī)的 hostname 是 host1,如果在這臺(tái)機(jī)器上的 /etc/hosts 文件里的信息如下所示:
清單 2:hosts 文件示例
…………….
host1 127.0.0.1
host1 9.123.245.91
……………..
|
也就是說(shuō)回環(huán)地址的記錄在實(shí)際的 IP 地址記錄之前,那么 loadavg 向 dmucs server 所發(fā)送的 IP 信息永遠(yuǎn)是 127.0.0.1!這樣就會(huì)導(dǎo)致 dmucs 程序不能正確識(shí)別這臺(tái)編譯主機(jī)。解決的方法就是將 /etc/hosts/ 文件里的 host 主機(jī)的實(shí)際 IP 信息記錄寫(xiě)在最前面。
下圖是 dmucs 程序啟動(dòng)后的情況,讀者可以從示例當(dāng)中看到有兩臺(tái)主機(jī)加入到了編譯集群:
圖 4 dmucs 程序運(yùn)行示例

最后用戶就可以使用“make CXX=gethost distcc”來(lái)啟動(dòng)編譯任務(wù)了。
編譯測(cè)試與性能分析
我們使用兩臺(tái)不同性能的工作機(jī)搭建分布式編譯的編譯集群,這兩臺(tái)工作機(jī)的配置參數(shù)如下表所示。
表 1:測(cè)試機(jī)配置表
? | 工作機(jī) 1 | 工作機(jī) 2 |
---|
CPU 數(shù)量 | 8 | 2 |
CPU 性能 | Intel(R) Xeon(R) CPU E5410 2.33GHz | Intel(R) Pentium(R) III CPU family 1266MHz |
內(nèi)存 | 16G | 1G |
IP 地址 | 9.47.98.90 | 9.47.98.59 |
每個(gè)主機(jī)上均運(yùn)行 distccd 程序“distccd -a 9.47.0.0/16”。其中 DMUCS 服務(wù)程序運(yùn)行在 8 核主機(jī)上,其 hosts-info 配置如下所示:
清單 3:hosts-info 配置
#Format:host-name number-of-cpus power-index
9.47.98.59 2 2
9.47.98.90 8 4
|
我們使用的測(cè)試項(xiàng)目包含個(gè)需要編譯 803 個(gè) CPP 文件,通常情況在單個(gè)普通雙核的工作機(jī)上編譯需要一小時(shí)以上。運(yùn)用此測(cè)試樣例,我們分別在單獨(dú)使用 distcc 和用 dmucs 進(jìn)行負(fù)載均衡優(yōu)化的情況下,進(jìn)行分布式編譯。測(cè)試環(huán)境的配置與上節(jié)內(nèi)容相同。通過(guò)設(shè)置不同的并行任務(wù)數(shù)量,編譯測(cè)試依次進(jìn)行并行任務(wù)數(shù)量為 2,4,6,8,10,12 和 14 的共 7 組測(cè)試。
我們得到了詳細(xì)的測(cè)試結(jié)果,請(qǐng)參見(jiàn)表 2。
表 2:測(cè)試結(jié)果
并行任務(wù)數(shù) | Distcc ( 分鐘 ) | distcc+dmucs ( 分鐘 ) |
---|
2 | 26.3 | 17.8 |
4 | 14.5 | 10.0 |
6 | 11.2 | 7.5 |
8 | 9.5 | 6.2 |
10 | 9.3 | 6.3 |
12 | 9.5 | 6.1 |
14 | 9.2 | 6.3 |
測(cè)試結(jié)果的數(shù)據(jù)對(duì)比請(qǐng)參加圖 5
圖 5:測(cè)試結(jié)果

通過(guò)對(duì)比,我們可以發(fā)現(xiàn)使用 dmucs 配合 distcc 在分布式編譯中進(jìn)行負(fù)載均衡,使性能強(qiáng)大的節(jié)點(diǎn)承擔(dān)較重的負(fù)載,可以大大提高編譯的效率,從而很好的解決了 distcc 平均分配所帶來(lái)的性能瓶頸問(wèn)題。從在本次測(cè)試中可以看出,其編譯時(shí)間平均縮短最多至 1/3 左右。
同時(shí),我們可以發(fā)現(xiàn)當(dāng)并行任務(wù)到 10 以后,編譯性能已無(wú)明顯提升。這也反應(yīng)了在分布式編譯中,并行并行編譯任務(wù)的數(shù)量應(yīng)該根據(jù)實(shí)際的編譯集群能力設(shè)定。單純提高并行編譯任務(wù)的數(shù)量而忽略編譯集群的實(shí)際能力將并不一定給編譯性能帶來(lái)好處。(責(zé)任編輯:A6)