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

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

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

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

最后用戶就可以使用“make CXX=gethost distcc”來啟動編譯任務了。
編譯測試與性能分析
我們使用兩臺不同性能的工作機搭建分布式編譯的編譯集群,這兩臺工作機的配置參數(shù)如下表所示。
表 1:測試機配置表
? | 工作機 1 | 工作機 2 |
---|
CPU 數(shù)量 | 8 | 2 |
CPU 性能 | Intel(R) Xeon(R) CPU E5410 2.33GHz | Intel(R) Pentium(R) III CPU family 1266MHz |
內存 | 16G | 1G |
IP 地址 | 9.47.98.90 | 9.47.98.59 |
每個主機上均運行 distccd 程序“distccd -a 9.47.0.0/16”。其中 DMUCS 服務程序運行在 8 核主機上,其 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
|
我們使用的測試項目包含個需要編譯 803 個 CPP 文件,通常情況在單個普通雙核的工作機上編譯需要一小時以上。運用此測試樣例,我們分別在單獨使用 distcc 和用 dmucs 進行負載均衡優(yōu)化的情況下,進行分布式編譯。測試環(huán)境的配置與上節(jié)內容相同。通過設置不同的并行任務數(shù)量,編譯測試依次進行并行任務數(shù)量為 2,4,6,8,10,12 和 14 的共 7 組測試。
我們得到了詳細的測試結果,請參見表 2。
表 2:測試結果
并行任務數(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 |
測試結果的數(shù)據對比請參加圖 5
圖 5:測試結果

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