分散コンパイルのためのdistcc


とある事情でMySQLを何度もビルドしなければならなくなりました(『MySQLデータベース構築バイブル』を読んだからではありませんが、そういうことにしておいてもらってもいいです)。

利用できる最速のマシン(Core i7 GHz)でMySQL 5.4.3-betaをビルドするのにかかる時間は1分40秒ほどです。1回だけならいいのですが、何回もすることを考えると、少しでも早くビルドできる環境を用意しておきたくなります。

Windowsの場合は知りませんが、GNU/LinuxやMac OS Xでは、C言語とC++の分散コンパイルのためのdistccが簡単に利用できるので試してみました。

手順は以下の通りです。

  1. すべてのマシンで同じコンパイラを利用できるようにする
  2. distccのインストール
  3. 環境変数CC、CXXの設定
  4. ./configure
  5. 環境変数DISTCC_HOSTSの設定
  6. make -jジョブ数

もう少し詳しく説明します。

利用したマシンは以下の通りです(A, D, Eのネットワークは1G、それ以外は100M)。

  • A: Core i7 940 2.93GHz
  • B: Core i5 750 2.67GHz
  • C: Core2 Quad Q6600 2.4GHz
  • D: Core2 4300 1.8GHz
  • E: Athlon 64 X2 5400+
  • F: Pentium D 3GHz

クライアント側で必要なのはdistcc、サーバ側で必要なのはdistcc-serverだけなのですが、マシンをどのように利用するかが確定しているわけではないので、まとめて入れておきます。

yum install distcc*

本稿執筆時点では、パッケージで配布されているdistccのバージョンは2.18でした。Googleによって改良されたバージョン3系列のほうが、パフォーマンスはいいはずなのですが、私の環境ではそれを確認することはできませんでした。

今回利用したGCCのバージョンは4.4.2です。すべてのマシンで、同じフルパスで同じバージョンのgcc, g++を利用できるようにしておきます(binutilsも?)。その上で、クライアントで次のように環境変数を設定します。

export CC="distcc gccのフルパス"
export CXX="distcc g++のフルパス"

さらに、利用するサーバとジョブの数を設定します(クライアントはIPではなくlocalhostと書くべきです)。ジョブ数のデフォルトはIPに対しては4、localhostに対しては2なので、その場合は「/ジョブ数」は省略できます。「IPアドレス/ジョブ数,lzo」と書くとデータを圧縮するようになりますが、私の環境ではその効果は確認できませんでした。

#distcc Ver. 2の場合
export DISTCC_HOSTS="サーバ1のIP/ジョブ数 サーバ2のIP/ジョブ数 ... localhost/ジョブ数"
#distcc Ver. 3の場合
export DISTCC_POTENTIAL_HOSTS="サーバ1のIP/ジョブ数 サーバ2のIP/ジョブ数 ... localhost/ジョブ数"

すべてのサーバ用マシンで、次のようにしてサーバを起動します(受け入れるジョブの最大値を指定することもできますが、この例のように省略すると、CPU数+2になります)。サブネットは192.168.0.0/24のように指定します(毎回これを行うのが面倒なら、distccdが自動的に起動されように設定しておいてもいいでしょう)。このサーバがポート3632を利用できるようにファイアウォールを設定しておいてください(利用するポートを指定することもできます)。

distccd --daemon --allow サブネット --log-stderr

マシンBをクライアントにして、DISTCC_HOSTSを「AのIP/6 EのIP CのIP localhost」にしたときのビルド時間は1分17秒でした。マシンA単体でのビルド時間1分41秒、マシンD単体でのビルド時間6分54秒と比べると、「速くなってよかったね」という感じです。

投入するジョブの数は微妙な調整が必要です。デフォルトの「CPU数+2」が最適というわけではありません。また、明らかに遅いマシン(ここではマシンF)は入れない方がよさそうです。

Macでも試してみました。Macの開発環境にはdistccがはじめから含まれているので、準備の手間が一つ省けます。

手元のMacBook Pro (Core2 Duo 2.16GHz)単体では、ビルドに7分57秒かかりました(GCCのバージョンは4.2.1)。

MacPortsという常にソースからビルドするシステムがデファクトになっているのでとても困ったことなのですが、私のMacのビルドは遅いです(MacPortsの待ち時間は大嫌いです)。たとえば、CPUはこのMacBook Proより遅いはずのマシンD(上述)のビルド時間は6分54秒でした(ディスクの書き込み速度の差はあまり関係ないでしょう)。

このMacBook Proをクライアントに、Core2 Duo 2GHzのMacBookとMacMiniをサーバにして(DISTCC_HOSTSは「MacBookのIP MacMiniのIP」)ビルド(make -j8)した結果は5分53秒でした(localhostをDISTCC_HOSTSに追加したら遅くなりました)。

確かに速くはなりましたが、「MacPortsがすごく速くなるかも」という期待は裏切られた感じです。

もちろん、GNU/Linuxマシン上にクロスコンパイル環境を構築してサーバにすればよいというのはわかっているのですが、Appleが開発環境を微妙にいじっているせいで、クロスコンパイル環境の構築はちょっとやっかいだったりするのです(そのためのプロジェクトがあるくらいに)。

『ジェネレーティブプログラミング』『C++ Template Metaprogramming』で紹介されているテンプレート/メタプログラミングのコードを使えば、分散コンパイルのインフラ上で並列計算ができたりするわけですが、そういうネタはまた別の機会に。

追記:Cプリプロセッサメタプログラミングで、文字列系泥沼関数型プログラミングというのもすごいですね。