読者です 読者をやめる 読者になる 読者になる

なんとなく

誰得感満載な記事が多いかも。Mono関係とLinuxのサーバ関係、レビューとか。

Raspberry PiとVPSなどで分散コンパイル

RapsberryPi distcc

もくじ

  • はじめに
  • ロスコンパイル環境の構築
  • distccで分散コンパイルさせるためのローカルの設定
  • pumpmodeについて
  • 計測
  • まとめ

はじめに

Raspberry Pi 3つで分散コンパイルに記述したように、Raspberry Pi 3台でdistccによる分散コンパイルが可能になった。
ただ、Raspberry Piだけではなくアーキテクチャ違う他のマシンも分散コンパイルに参加させたいなと思いクロスコンパイル環境を作成した。
そして、distccで分散コンパイルに参加させてコンパイル時間を計測してみたという話。
Raspberry Pi Type B 512MB

ロスコンパイル環境の構築

まずは、クロスコンパイル環境を作る必要がある。
Raspberry PiのCPUはARMであるが、そうではない環境でARMの環境で稼働するようバイナリをコンパイルできるようにする。

How to:Raspberry Pi(Raspbian”Wheezy”)用にクロスコンパイル環境を構築する
を参考にさせてもらい環境を作成した。

crosstool-ngというものを利用すれば簡単にクロスコンパイル環境が作成できるようだ。

ここではVMware Playerを利用し、x86_64でdebian7な環境(CPUx4,HDD:40G,RAM:4GB)を準備しておく。VPSでつくってもいい。

ロスコンパイル環境作成の前準備

まずは、crosstool-ngの環境を作るために必要なものをインストールする。

$ su -
# aptitude install gperf bison flex gawk libtool automake autoconf

次に、クロスコンパイル環境を格納するためのフォルダを作成する。

# mkdir /opt/cross

自身が書き込めるための権限を付けておく。

# chown takeshich:takeshich -R /opt/cross

ロスコンパイル環境を準備するためのフォルダを作成し、移動する。

$ cd ~
$ mkdir crosspre
$ cd crosspre/

crosstool-ngをダウンロードし、解凍する。

$ wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.19.0.tar.bz2
$ tar xvfj crosstool-ng-1.19.0.tar.bz2

作成したクロスコンパイル環境を格納するためのパスを指定して、make,make installを行う。

$ cd crosstool-ng-1.19.0
$ ./configure --prefix=/opt/cross
$ make
$ su -
# make install

crosstool-ngカーネルのコンパイルと同様に.configを使用したmenuconfigが使用できる。
そのバイナリであるct-ngのあるところにパスを通す。

$ export PATH=$PATH:/opt/cross/bin

これで、クロスコンパイル環境のビルドするための前準備が終わったので、次にクロスコンパイル環境のビルドに移っていく。

ロスコンパイル環境作成

以下のスレッドにあるRaspberry Pi用の.configファイルをダウンロードして使う。
http://www.raspberrypi.org/phpBB3/viewtopic.php?t=11315&p=129461

$ wget http://www.raspberrypi.org/phpBB3/download/file.php?id=589
$ mv file.php?id=589 temp.tar.gz
$ tar xvfz temp.tar.gz

変更点は、rasbianのgccが4.7.2なのでgccを4.7にする点。
linaroはARM用linuxについて最適化を図っている組織のようだ。

$ ct-ng menuconfig

メニューで
C compiler --->
gcc version (linaro-4.8-2013.06-1) --->
linaro-4.7-2013.06-1
に変更する。

また、jobについては、CPUとメモリに合わせて変更すると速度が改善されると思われる。

ビルドを開始する。

$ ct-ng build

当方のVMware Playerの環境では40分ぐらいかかった。

ロスコンパイル環境の設定及び確認

次にPATHをとおす

# vi /etc/profile
CROSS_ROOT="/opt/cross/x-tools/arm-unknown-linux-gnueabi"
CROSS_PATH="${CROSS_ROOT}/bin:${CROSS_ROOT}/libexec/gcc/arm-unknown-linux-gnueabi/4.7.4:${CROSS_ROOT}/arm-unknown-linux-gnueabi/bin"
export PATH="${CROSS_PATH}:${PATH}"
# source /etc/profile

テストをする。
helloworldを出力するものをコンパイルし、実行できるか確認する。

$ vi helloworld.c
#include <stdio.h>
 
int main(void)
{
    printf("Hello, world!");
    return 0;
}

コンパイルできることを確認する。

$ /opt/cross/x-tools/arm-unknown-linux-gnueabi/bin/arm-unknown-linux-gnueabi-gcc ./helloworld.c -o ./helloworld

ARMではない環境で実行し、怒られることを確認する。

$ ./helloworld 
-bash: ./helloworld: cannot execute binary file

raspberrypiに持ってきて、実行する。

takeshich@pi2 ~ $ ./helloworld 
Hello, world!takeshich@pi2 ~ $ 

これで、クロスコンパイル環境ができた。

distccで分散コンパイルさせるためのローカルの設定

ローカルの設定で重要な点について

前提として、Raspberry Pi 3つで分散コンパイルで設定したように、Raspberry Pi同士では分散コンパイルできるのが確認されている状態というところからスタートする。

実は、設定はかなりハマって、いろいろ試した。

当初Gentooのwikiを参考にしていたのだけれども、debianのはGentooのようには行かなかった。

結局わかったことは、以下2点。

  • リモート(分散先)に存在するコンパイラと同名のものをローカルで明示的に指定する必要がある。かつ、もちろん、ローカルにも同名のコンパイラが存在する必要がある。
  • バージョンは、*.*まで(例4.7)まであっていればコンパイルは出来る。
CC="distcc arm-unknown-linux-gnueabi-gcc"

上記のように、明示的に、リモート(分散先)に存在しPATHが通っており、実行できるものを記述する必要がある。ローカルにも同名のコンパイラが存在する必要がある。

CC=distcc

のみだとGentooのwikiに書いてあるようなwrapperのシェルを作っても、debianではリモート(分散先)のサーバにはccが飛ぶようである。

distccのドキュメントによれば、

If you build gcc from source, you should use the --program-suffix configuration options to cause it to be installed with a name that encodes the gcc version and the target platform.
The recommended convention for the gcc name is TARGET-gcc-VERSION such as i686-linux-gcc-3.2 . GCC 3.3 will install itself under this name, in addition to TARGET-gcc and, if it's native, gcc-VERSION and gcc .
The compiler must be installed under the same name on the client and on every volunteer machine.

http://distcc.googlecode.com/svn/trunk/doc/web/man/distcc_1.html

とある。

上記を踏まえ、ローカルのコンパイラにリモート(分散先)にあるコンパイラと同名のシンボリックリンクを作成し、対応した。

CC="distcc arm-unknown-linux-gnueabi-gcc" make -j4

として、ローカルでもリモートでも対応したコンパイラが動くようにするということである。

ローカルの設定

もし、gcc4.7がインストールされていないのであれば、gcc4.7.*においてコンパイルしたいので、インストールする。

$ sudo aptitude install gcc-4.7 g++-4.7

リモート(分散先)のクロスコンパイル環境のコンパイラがarm-unknown-linux-gnueabi-gccなのでローカルのコンパイラシンボリックリンクの作成する。

rasbianではコンパイラは以下のようになっている。

$ cd /usr/bin
takeshich@pi2 /usr/bin $ ls -la arm*
lrwxrwxrwx 1 root root       7 1118  2012 arm-linux-gnueabihf-cpp -> cpp-4.6
lrwxrwxrwx 1 root root       7  115  2013 arm-linux-gnueabihf-cpp-4.6 -> cpp-4.6
lrwxrwxrwx 1 root root       7  120  2013 arm-linux-gnueabihf-cpp-4.7 -> cpp-4.7
lrwxrwxrwx 1 root root       7 1118  2012 arm-linux-gnueabihf-g++ -> g++-4.6
lrwxrwxrwx 1 root root       7  115  2013 arm-linux-gnueabihf-g++-4.6 -> g++-4.6
lrwxrwxrwx 1 root root       7  120  2013 arm-linux-gnueabihf-g++-4.7 -> g++-4.7
lrwxrwxrwx 1 root root       7 1118  2012 arm-linux-gnueabihf-gcc -> gcc-4.6
lrwxrwxrwx 1 root root       7  115  2013 arm-linux-gnueabihf-gcc-4.6 -> gcc-4.6
lrwxrwxrwx 1 root root       7  120  2013 arm-linux-gnueabihf-gcc-4.7 -> gcc-4.7
lrwxrwxrwx 1 root root      10  120  2013 arm-linux-gnueabihf-gcc-ar-4.7 -> gcc-ar-4.7
lrwxrwxrwx 1 root root      10  120  2013 arm-linux-gnueabihf-gcc-nm-4.7 -> gcc-nm-4.7
lrwxrwxrwx 1 root root      14  120  2013 arm-linux-gnueabihf-gcc-ranlib-4.7 -> gcc-ranlib-4.7
-rwxr-xr-x 1 root root 1158420  29  2013 arm-linux-gnueabihf-run

このままでは、コンパイラの名前が異なってしまうので、distccにおいてはうまく実行されない。
そこで、以下のようにリモート(分散先)のクロスコンパイル環境のコンパイラと同じ名前のシンボリックリンクを作成する。

$ sudo ln -s cpp-4.7 arm-unknown-linux-gnueabi-cpp
$ sudo ln -s cpp-4.6 arm-unknown-linux-gnueabi-cpp-4.6
$ sudo ln -s cpp-4.7 arm-unknown-linux-gnueabi-cpp-4.7
$ sudo ln -s g++-4.7 arm-unknown-linux-gnueabi-g++
$ sudo ln -s g++-4.6 arm-unknown-linux-gnueabi-g++-4.6
$ sudo ln -s g++-4.7 arm-unknown-linux-gnueabi-g++-4.7
$ sudo ln -s gcc-4.7 arm-unknown-linux-gnueabi-gcc
$ sudo ln -s gcc-4.6 arm-unknown-linux-gnueabi-gcc-4.6
$ sudo ln -s gcc-4.7 arm-unknown-linux-gnueabi-gcc-4.7

また、リモート(分散先)のクロスコンパイル環境では、gccは4.7にしたのでローカルも同様にする。
ローカルで確認したところ、gcc4.6なので4.7にシンボリックリンクを変更する。

$ sudo rm gcc g++ cpp
$ sudo ln -s gcc-4.7 gcc
$ sudo ln -s g++-4.7 g++
$ sudo ln -s cpp-4.7 cpp
$ sudo rm arm-linux-gnueabihf-cpp arm-linux-gnueabihf-g++ arm-linux-gnueabihf-gcc
$ sudo ln -s cpp-4.7 arm-linux-gnueabihf-cpp
$ sudo ln -s g++-4.7 arm-linux-gnueabihf-g++
$ sudo ln -s gcc-4.7 arm-linux-gnueabihf-gcc

設定は以上。
シンボリックリンクを作成し、リモート(分散先)のクロスコンパイル環境と同名で動くコンパイラを作り上げる。

稼働確認

リモート(分散先)のクロスコンパイル環境のサーバには、distccがインストールされている。
仮にIPアドレスが、192.168.11.9だとすると以下のようになる。

$ export DISTCC_HOSTS="192.168.11.9"
$ CC="distcc arm-unknown-linux-gnueabi-gcc" ./configure
$ CC="distcc arm-unknown-linux-gnueabi-gcc" make -j4

のようにしてコンパイルする。

ローカルで

$ distccmon-text 1 

として、確認する一方で、

# tail -f /var/log/distccd.log

としてリモート(分散先)のサーバの/var/log/distccd.logでCOMPILE_OKが出力されるのを確認する。

詳しくは、計測のところで記述するが、distccの通常モードではpreprocessはローカルで行い、分散先のサーバでコンパイルしている。
distccのversion3以降は、pumpモードでpreprocessについても分散先で処理できる。
raspberry piは非力なのでpumpモードを使用することにさらなるスピードアップが望めるかもしれない。

PUMPモード

distccのversion3以降は、pumpモードでpreprocessについても分散先で処理できる。
そのためpumpモードを試してみた。

設定については、クライアント側でのインストールのみで、サーバ側で設定する必要はない。

インストール

$ sudo aptitude install distcc-pump

明示的に指定する場合は、以下のようにする。

$ export DISTCC_HOSTS="www.dvgtu.org/12,cpp,lzo 192.168.11.9/4,cpp,lzo 192.168.11.7/1,cpp,lzo"

cppをつけることにつけることによってpumpモードになるようだ。
詳しくは、マニュアルを参照してほしい。

ランダムでも構わないのであれば、

$ export DISTCC_POTENTIAL_HOSTS="192.168.11.9 192.168.11.7"

のようにする。--randomizeとcpp,lzoなどのオプションを付与してくれるようだ。

make時は、以下のようにする。

$ CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j18

試しに計測していたら、以下のエラーがたくさん出た。

WARNING include server: Preprocessing locally. Include server not covering: Bailing out because include server spent more than 3.8s user time handling request for translation unit 'base64.c'
distcc[6365] Warning: include server gave up analyzing

回避方法は以下

You could try increasing USER_TIME_QUOTA in include_server/basics.py.

https://code.google.com/p/distcc/issues/detail?id=40

対応したら、出なくなった。

また、ソースによっては、pumpモードだとうまくコンパイルできないものもあった。
Raspberry Pi 3つで分散コンパイルにおいて試しにコンパイルしていたcurlは、うまくコンパイルできなかった。

pumpモードで実行されているかどうかについて、サーバ側では、--log-level debugにし、ログでヘッダが数十送られてくるので確認することができる。

計測

Raspberry PiをThin Clientとして使おうと思って(xfreerdpのコンパイル)でコンパイルに使用したxfreerdpで計測した。

RaspberryPiだけの環境での計測

計測結果を表にしてみた。

モード 内容 real time user time system time
RaspberryPi単独 21m17.529s 14m19.190s 1m14.750s
ノーマル 2台 11m13.198s 9m31.080s 1m9.460s
ノーマル 3台 10m10.065s 8m4.610s 1m9.030s
PUMP 2台 10m43.794s 7m51.100s 1m1.010s
PUMP 3台 10m10.840s 7m39.980s 1m2.370s

当方にある3台では、ノーマルモード、pumpモードでの速度差についてはなんとも言えない。
2台の場合、pumpモードが30秒ほど速くなるが、3台になるとほぼ同じ速度になる。
3台目に割り当てているマシンが負荷が高い処理をしている可能性が高いので、こういう結果なのだろう。

実行コマンド

  • RaspberryPi単独
$ CC=gcc cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_SSE2=OFF -DWITH_NEON=OFF
$ time make
  • ノーマル2台
$ CC="distcc arm-unknown-linux-gnueabi-gcc" cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_SSE2=OFF -DWITH_NEON=OFF
$ export DISTCC_HOSTS="localhost 192.168.11.8"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j2

以降Makefileは同じ

  • ノーマル3台
$ export DISTCC_HOSTS="localhost 192.168.11.8 192.168.11.6"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j4
  • pumpモード2台
$ export DISTCC_HOSTS="localhost/1,cpp,lzo 192.168.11.8/1,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j4
  • pumpモード3台
$ export DISTCC_HOSTS="localhost/1,cpp,lzo 192.168.11.8/1,cpp,lzo 192.168.11.6/1,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j4
RaspberryPiとVPSで計測

借りているVPSで設定して、分散先に追加し数多く計測してみたのだが、Raspberry Piにコンパイルに参加させると速度が遅くなる場合があった。そこで、VPSとlocalhost,VPSのみをコンパイルして比較してみた。

モード 内容 real time user time system time
ノーマル localhostあり 6m6.329s 4m32.900s 1m3.760s
ノーマル localhostなし 5m58.085s 4m25.290s 1m2.810s
PUMP localhostあり 6m25.349s 4m37.600s 1m2.980s
PUMP localhostなし(job16) 6m11.400s 4m23.350s 1m2.570s
PUMP localhostなし(job24) 6m1.164s 4m10.140s 1m2.120s

RaspberryPiをコンパイルに参加させると遅くなるようだ。
また、ノーマルモードのほうがpumpモードより早い結果になっている。
job数を増やしてみると、pumpモードを計測したところ若干速度が上がった。
pumpモードはjob数が多い場合有効なようである。

実行コマンド

$ export DISTCC_HOSTS="www.dvgtu.org/16,lzo localhost/1"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j16
$ export DISTCC_HOSTS="www.dvgtu.org/16,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j16
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo localhost/1,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j16
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j16
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j24

色々計測していて、判明したのだが、LIMITオプション(IPアドレスのスラッシュの後の数字)を適切につけないと

モード 内容 real time user time system time
ノーマル localhostありLIMITなし 7m10.475s 5m28.620s 1m11.590s
$ export DISTCC_HOSTS="www.dvgtu.org localhost"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j16

という結果になる。
リモート(分散先)の性能が異なる場合は、オプションを明示的にDISTCC_HOSTSで指定しないと速度が大幅に変わるようだ。

RaspberryPiは配信のみで使用し、分散先にはVPSとPC上のVMWare Playerのlinuxで計測

RaspberryPiとVPSで計測でわかったように、RaspberryPiをコンパイルに参加させてしまうと処理が遅くなる可能性があるので、RaspberryPiは配信のみ使用し、VPSとVMWare Playerのlinux(以下VM)を分散先に参加させて計測してみた。

モード 内容 real time user time system time
ノーマル VPS+VM 6m0.747s 4m20.330s 1m9.950s
ノーマル VPS+VM2台 5m58.508s 4m20.890s 1m8.950s
PUMP VPS+VM 5m58.791s 4m12.210s 1m0.680s
PUMP VPS+VM2台 5m58.503s 4m8.660s 1m1.870s

結果は、すべて6分程度で誤差の範囲だろう。いずれのモードにおいても、もうCPUの数を増やしても速度が向上しないようだ。
おそらく、配信元の処理能力の限界のだろう。予想するにノーマルモードに関してはpreprocessの処理速度、pumpモードに関しては、ソースの解析の処理速度の限界なのであろう。

実行コマンド

  • ノーマルモードでVPS+VM
$ export DISTCC_HOSTS="www.dvgtu.org/16 192.168.11.9/4"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j20
  • ノーマルモードでVPS+VM2台
$ export DISTCC_HOSTS="www.dvgtu.org/16 192.168.11.9/4 192.168.11.4/4"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j24
  • PUMPモードでVPS+VM
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo 192.168.11.9/4,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j20
  • PUMPモードでVPS+VM2台
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo 192.168.11.9/4,cpp,lzo 192.168.11.4/4,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j24
配信元(ローカル)の処理の上限について

分散先にはVPSとPC上のVMWare Playerのlinuxで計測してみて、おそらく配信元(ローカル)の処理の上限だろうと考えた。
そこで、ノーマルモードは、job数を減らしても、速度が出るか?もし、速くなれば上限ではないはずである。
pumpモードは、job数を増やせば、速度が出るか?もし、速くなれば上限ではないはずである。
以上、2点について確認してみた。

モード 内容 real time user time system time
ノーマル VPS(job16) 5m58.085s 4m25.290s 1m2.810s
ノーマル VPS(job8) 5m57.878s 4m25.770s 1m2.220s
PUMP VPS(job16) 6m11.400s 4m23.350s 1m2.570s
PUMP VPS(job32) 6m5.143s 4m14.540s 1m3.970s

ノーマルモードでは、jobを16から8に減らして計測してみた。
速度はほぼ変わらず、配信元のpreprocessの速度の上限と言って間違いなさそうだ。
また、pumpモードでは、job数を16から32に増やして計測してみた。
速度は、若干あがったが、6mを超えるほどではなかった。やはりソースの解析についても配信元の処理能力の上限なのだろう。

ノーマルモードよりpumpモードの方がpreprocessも分散できるのでパフォーマンスの向上が期待できるかもということに対しては、今回のテスト環境においてRaspberryPiではパフォーマンスの向上は期待できないようである。使用したソースも影響してくることであろう。

実行コマンド

  • ノーマルモードで、VPSでjob16
$ export DISTCC_HOSTS="www.dvgtu.org/16,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j16
  • ノーマルモードで、VPSでjob8
$ export DISTCC_HOSTS="www.dvgtu.org/8,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" make -j8
  • pumpモードで、VPSでjob16
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j16
  • pumpモードで、VPSでjob32
$ export DISTCC_HOSTS="www.dvgtu.org/16,cpp,lzo"
$ time CC="distcc arm-unknown-linux-gnueabi-gcc" distcc-pump make -j32

まとめ

distccで分散コンパイルを行う場合、配信元もある程度処理能力の高いものでないと用意された分散先の能力をフルに使うことはできず、あまり速度がでない。
処理能力の低い分散先を用意するとかえって遅くなる。このことから、RaspberryPiは速度を求める場合のdistccの使用には向かないといえる。

ただし、RaspberryPiのみでのコンパイルよりはdistccを使用すると速くなる。
今回はクロスコンパイル環境のみでの計測をしていないが、おそらく、makeの速度では分散せずにVPSなどでのクロスコンパイルの環境でコンパイルする方が速いだろう。
ファイルの転送やインストールの煩雑さとRaspberrypi上からコンパイルする容易さを勘案し、使用するのが良いと思う。

当方の環境ではRaspberrypiからノーマルモードでdistccをVPSに丸投げ方式が一番パフォーマンスが良いようだ。適切なオプションを指定する必要はある。

今回、クロスコンパイル環境を含めたdistccによる分散環境を構築したら、Raspberry Pi 3つで分散コンパイルにおいて構築した環境がやっぱり活躍しないということがわかってしまった。

今回の対象はRaspberryPiであったが、この方法は他の環境に応用出来るので、とても勉強になった。