2016年2月20日 星期六

icecream / icecc: 分散式編譯系統簡介

  • 簡介
Icecream 是由 SUSE 基於 distcc 所開發出來的, icecream 與 distcc 要處理的問題類似, 都是想要將編譯工作分散到不同機器上平行處理, 但與 distcc 的不同在於 icecream 採用的是中央集權的主從式架構, 透過中央的 scheduler server 來動態分配編譯工作, 並且會優先分配到速度較快閒置機器進行編譯, 並且最重要的是對於 Cross Compilation, 與 distcc 相比 icecream有較佳的支援.

  • 安裝
基本上應該透過系統內建的套件系統都可以安裝成功, 非不得已的話再自己編譯吧, 以下列幾個常見系統的安裝方法:
Debian/Ubuntu
$ sudo apt-get install icecream
Fedora
$ sudo dnf install icecream
CentOS
$ sudo yum install icecream

  • icecream scheduler server 設定
scheduler server 打起來後基本上什麼都不用設定, 整個網路環境中只要有一個 scheduler 即可, 打起來後會定期發送廣播封包告訴大家這邊有一個便宜好用的 icecream scheduler 快來連它!
如何啟動
  • Ubuntu/CentOS
service icecc-scheduler start
  • Fedora
systemctl start icecc-scheduler.service

  • icecream node 設定
Configure file 位置
  • Ubuntu
/etc/icecc/icecc.conf
  • Fedora/CentOS
/etc/sysconfig/icecream
Configure file 內容
ICECREAM_NETNAME : 這台機器的名稱, 預設值會去抓本機的 hostname
ICECREAM_SCHEDULER_HOST : 指定要使用哪個 Scheduler server, 不設定的話 iceccd 會自己去找區網上的其中一個, 在一般情況底下留白讓 iceccd 自行搜索即可
ICECREAM_ALLOW_REMOTE : 是否允許其他機器利用本機端來編譯, 預設值通常是 yes
ICECREAM_MAX_JOBS : 這台機器最多負載的編譯工作數目, 預設值會根據該機器上的處理器數量來決定

如何啟動
  • Ubuntu/CentOS
$ service iceccd start
  • Fedora
$ systemctl start iceccd

  • icecc 使用
基本使用
icecc 跟 distcc 或 ccache 使用方法相當類似, 都是透過 icecc 再呼叫 gcc/clang 的方式運作, 例如 ln -s /usr/bin/icecc gcc, 但通常 package system 裝來的會順便幫忙建立 symbol link, 例如 Fedora 22 放在 /usr/libexec/icecc/bin/gcc 以及 /usr/libexec/icecc/bin/g++, Ubuntu 14.04 則放在 /usr/lib/icecc/bin/gcc 以及 /usr/lib/icecc/bin/g++.

基本上最方便的使用方式就是將 icecc 的 wrapper 放到 path 裡面即可, 或著是手動指定 CC= CXX=

例如:
/usr/libexec/icecc/bin/gcc foo.c -c
或著是
export PATH=/usr/libexec/icecc/bin/:$PATH
gcc foo.c -c
都可以透過 icecc 來進行編譯

當然初期使用的時候會有點疑惑, 真的有使用到 icecc 來達到分散式編譯嗎? 這邊提供一個基本的測試方法, 就是設定 ICECC_DEBUG 這個環境變數, 讓 icecc 吐出一些 debug 訊息看一下運作情況如何:
$ ICECC_DEBUG=1 gcc hello.c  -c
ICECC[27118] 17:17:34: connected to /var/run/icecc/iceccd.socket
ICECC[27118] 17:17:34: native /var/cache/icecream/native/3686f16c4bdce474f4c0445c4b27e08e.tar.gz
ICECC[27118] 17:17:34: env: x86_64 '/var/cache/icecream/native/3686f16c4bdce474f4c0445c4b27e08e.tar.gz'
ICECC[27118] 17:17:34: hello.c compiled 1 times on x86_64
ICECC[27118] 17:17:34: Have to use host 192.168.1.3:10245 - Job ID: 4361 - env: x86_64 - has env: true - match j: 0
ICECC[27118] 17:17:35: connected to 192.168.1.3
ICECC[27118] 17:17:35: <send compile_file>
ICECC[27118] 17:17:35: </send compile_file: 0ms>
ICECC[27118] 17:17:35: <write_server_cpp from cpp>
ICECC[27118] 17:17:35: sent 4963 bytes (29%)
ICECC[27118] 17:17:35: </write_server_cpp from cpp: 9ms>
ICECC[27118] 17:17:35: <wait for cpp>
ICECC[27118] 17:17:35: </wait for cpp: 0ms>
ICECC[27118] 17:17:35: <wait for cs>
ICECC[27118] 17:17:35: </wait for cs: 202ms>
ICECC[27118] 17:17:35: got 623 bytes (41%)


Cross Compilation
Cross compilation 部份請留意 icecream 版本新舊的問題, 版本在 1.x 以後這部份功能較為堪用, 試過 CentOS 6 上 0.9.x 版本的會遇見許多靈異現象, 所以請確定所有 icecream node 版本至少都是 1.x, 當然最好的情況是所有 node 上的環境都一樣是最理想的.

透過 icecc 來使用 cross compilation 最困難的一關就是剛開始建立 wrapper, 沒有辦法像使用 native/host gcc 一樣直接 ln -s /usr/bin/icecc gcc 就開工了, 必須透過較複雜的方式建立 wrapper 來以利後續使用, 而接下來的段落將會介紹如何製作 wrapper.

進階 icecc wrapper 製作
雖然 icecc 可以直接將編譯工作丟到其他台機器上編譯, 但若其他機器環境中的 gcc 與 binutils 版本不盡相同的話將會相當棘手, 會造成 A 檔案丟到 X 機器編譯與在本地編譯的結果不一樣, 而理論上 A 檔案不論在本機端或著是任何一台遠端機器上編譯的結果都要一模一樣才行, 否則將會造成編譯的不穩定性.

而解決這問題最直覺的方式便是將本地端的 Compiler 包含 Assembler 傳送到遠端的機器, icecream 在這邊則處理的更細緻, 除了 Compiler 以及 Assembler 外其所有相依的 Shared Library 也都會傳送到遠端, 避免 Library 版本不同進而造成 Compiler 本身行為不同(最常見的例子是 C Library 中的 qsort 可能在不同 libc 中實做不一樣, 造成排序結果相異).

Icecream 提供了一個小工具  icecc-create-env 來打包上述所提到的東西, 並且在運作 icecc 時透過設定 ICECC_VERSION 環境變數來指定使用的環境是哪一包, 接下來就來介紹 icecc-create-env 如何使用以及所造出來的那包環境如何使用, 並且使用 aarch64 的 linaro toolchain 作為範例來展示結合 icecc 編譯.

  • icecc-create-env
icecc-create-env 共有兩種模式 gcc 以及 clang 模式, 主要是針對目前主流的兩大 Open Source compiler 作不同的支援, 

使用方式
/usr/bin/icecc-create-env --gcc
或著
/usr/bin/icecc-create-env --clang

範例輸出:
$ icecc-create-env --gcc bin/aarch64-linux-gnu-gcc bin/aarch64-linux-gnu-g++
adding file /bin/true
adding file /lib64/libc.so.6
adding file /lib64/ld-linux-x86-64.so.2
adding file /usr/bin/gcc=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc
adding file /lib64/libm.so.6
adding file /usr/bin/g++=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/aarch64-linux-gnu-g++
adding file /usr/bin/cc1=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/../libexec/gcc/aarch64-linux-gnu/5.1.1/cc1
adding file /lib64/libdl.so.2
adding file /usr/bin/cc1plus=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/../libexec/gcc/aarch64-linux-gnu/5.1.1/cc1plus
adding file /usr/bin/as=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/../lib/gcc/aarch64-linux-gnu/5.1.1/../../../../aarch64-linux-gnu/bin/as
adding file /lib64/libz.so.1
adding file /usr/libexec/gcc/aarch64-linux-gnu/5.1.1/liblto_plugin.so=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/../libexec/gcc/aarch64-linux-gnu/5.1.1/liblto_plugin.so
adding file /etc/ld.so.conf=/tmp/icecc_ld_so_confJFqMdV
cp: omitting directory ‘/lib’
cp: omitting directory ‘/lib64’
creating 5a1f48aacc455db5ff0d0104a827651c.tar.gz


可以透過輸出大致看出其運作過程, 其中最後一行就是整個打包完的壓縮檔 5a1f48aacc455db5ff0d0104a827651c.tar.gz, 預設檔名是所有檔案的 md5 hash 出來的, 不過其實檔名不重要, 可以改名成人比較看得懂的檔名沒關係, 例如 aarch64-linux-gnu.icecc.tar.gz

  • ICECC_VERSION
透過 icecc-create-env 設定好後, 開一個文字檔, 內容如下:

# /usr/bin/bash
export ICECC_VERSION=/home/kito/arm-toolchain/aarch64-linux-gnu/aarch64-linux-gnu.icecc.tar.gz
export ICECC_CC=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc
export ICECC_CXX=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/aarch64-linux-gnu-g++
icecc aarch64-linux-gnu-gcc "$@"

然後將檔名存成 aarch64-linux-gnu-gcc, 然後如法炮製作個 aarch64-linux-gnu-g++

# /usr/bin/bash
export ICECC_VERSION=/home/kito/arm-toolchain/aarch64-linux-gnu/aarch64-linux-gnu.icecc.tar.gz
export ICECC_CC=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc
export ICECC_CXX=/home/kito/arm-toolchain/aarch64-linux-gnu/bin/aarch64-linux-gnu-g++
icecc aarch64-linux-gnu-g++ "$@"


然後記得開執行權限

$ chmod +x aarch64-linux-gnu-gcc aarch64-linux-gnu-g++

接著這兩個 wrapper 就能跟 native/host 的 icecc wrapper 一樣好用了!

  • icecream 運作原理
大致上的運作流程如下
  1. gcc foo.c -c
  • 其中 gcc 是個 icecc wrapper
  1. 將 foo.c 進 Pre-processor 處理掉 marco
  1. 將上一步的結果丟給本機的 iceccd
  1. iceccd 聯絡 icecc-scheduler 問一下誰很閒
  1. iceccd 將Pre-process 過的 source code 傳送過去
  1. 遠端的 iceccd 將收進來的 source code 進行編譯
  1. 將上一步所產出的 .o 檔傳回本機
  • icecream 使用限制
icecream 受限於架構限制會有一些使用上的限制, 主要是遇到某些編譯選項時會無法分散編譯工作出去:
  • 沒有 -S 或 -c : 這代表編譯器順便也要完成鍊結的工作, 但 icecream 的架構鍊結只能在本地端完成
  • 有下 -B 參數 : -B 參數是個少見的參數但在某些 Open source project 會使用到, 這個將會影響到 gcc/clang 的搜索路徑, 甚至可能不使用編譯器預設的 cc1/cc1plus, 因此保險起見 icecream 看到此參數會將編譯工作留在本地端
  • 多個 input file : 沒啥原因, 單純就 icecc 沒支援
  • 其它會造成編譯器在編譯過程中產生額外檔案的行為都會在 local 端執行, 例如 dump 編譯器內部相關 debug 資訊.

  • icecream 編譯農場建設指南
  • 良好的區網, 基本配備是 100Mb 有 1Gb 則是最好, 不然會加速不成又把網路癱瘓
  • 整體環境相似最好一樣, 例如 Linux kernel 版本及 Distribution 一樣,
  • Note 1: 測試環境有試過 Ubuntu 14.04, Fedora 22, Gentoo, CentOS 6 混在一起還是可以運作的不錯.
  • Note 2: 環境混搭的話請一定要設定 ICECC_VERSION, 否則容易遇到 GCC 版本不同問題, 造成不同 node 間 input 一樣但 output 的 object file 不一樣甚至是不相容的東西就 GG 了
  • 官網上說可以跨系統, 例如 OSX 與 Linux, 但手邊都 Linux 沒環境測試, 可以參考官方文件設定試試看
  • 效能觀察工具 - icemon
  • Ubuntu:
sudo apt-get install icemon
  • Fedora/CentOS:
Package system 沒提供, 需要自行編譯

  • 實驗數據 : 透過 icecream 編譯 LLVM
實驗環境
  • 硬體環境
  • CPU : E3-1230 v3 @ 3.30GHz (4 core with HT = 8 logic core)
  • RAM : 32GB
  • Network : 1 Gigabit Ethernet
  • 軟體環境
  • OS : Fedora 22 / Gentoo
  • ICECC version : 1.0.90
  • GCC : 4.9.3
  • ICECREAM 組態
  • ICECREAM_MAX_JOBS=12
  • 實驗目標
LLVM Version: r26441
  1. 編譯 LLVM Debug build
  • Configure as : -DCMAKE_BUILD_TYPE=Debug -DLLVM_OPTIMIZED_TABLEGEN=ON
  • time make -j48
  1. 編譯 LLVM Release build
  • Configure as : -DCMAKE_BUILD_TYPE=Release
  • time make -j12
Icecream build:
  • -DCMAKE_C_COMPILER=/usr/libexec/icecc/bin/gcc
  • -DCMAKE_CXX_COMPILER=/usr/libexec/icecc/bin/g++
GCC build:
  • -DCMAKE_C_COMPILER=/usr/bin/gcc
  • -DCMAKE_CXX_COMPILER=/usr/bin/g++

實驗數據


LLVM Build Type icecc gcc Speed up Total file size
for build folder
Release 2m57.275s 10m34.657s 3.6x 919M
Debug 5m10.249s 12m2.120s 2.3x 12G

LLVM Build TypeiceccgccSpeed upTotoal file size for build folder
Release2m57.275s10m34.657s5.4x919M
Debug5m10.249s12m2.120s2.3x12G
在實驗數據中可以觀察到 icecc 在有充足的機器資源下可以加速相當多, 與理想值四倍相差不遠.

但有趣的是在 Debug build 的時候 icecc 版本明顯比 Release 版本慢了接近一倍, 與普通情況相反, 通常 Release build 編譯器要進行最佳化應該會較 Debug build 慢上許多, 其中原因可以合理推測是因為 Debug build 出來的 object file 肥大許多, 可以看到上列表格中 build 的資料夾大小差了近 12 倍, 因此檔案傳輸時間也將整體效能往下拖了不少.

  • 結語
整體而言 icecc 是相當好用的分散式編譯工作, 個人使用經驗上來說比 distcc 來說還好用, 尤其是工作環境上需要使用不同編譯器以及機器上沒有完全一致的環境以及同步的檔案系統的時候.

  • Reference