distccでコンパイル時間を削減する

高速でフリーの、C/C++分散コンパイル

Comments

オープンソースのソフトウェアという性格から、Linux ™ のアプリケーションの多くは「tarball」として配布されます。tarballに含まれるソースコードは、アプリケーション実行前にビルドが必要です。大きなアプリケーションになると、ビルドするのに数時間もかかります。この記事では分散Cコンパイラーであるdistccの使い方をとりあげ、ソースをより早く利用できるようにコンパイル・スピードを上げるにはどうすべきかを解説します。

Tarballの利点

一部のLinuxアプリケーションはRPM(Red Hat Package Manager)ファイルとして入手できます。こうしたファイルを使うことでエンドユーザーは一般的に、アプリケーションを素早く使用可能な状態に立ち上げることができます。ところが、特にオープンソースのソフトウェアの場合にはしばしば、a .tar.gz (「tarball」)を使うという選択肢があります。エンドユーザーはtarballに含まれるソースコードをビルドするのです。tarballを使うと、RPMでの場合よりも設定が少し複雑になりますが、いくつか利点もあります。

  • アプリケーションは通常、/usr/localにインストールされます。つまり自分のマシンにインストール・ファイルを単純に放り込んでも、相変わらず自分のアプリケーションを保持することができます。
  • 自分にうまく合うように、コードに小細工をすることができます。
  • 多くの最適化プログラムがあります。

最後の点が私の気に入っているものです。私自身はちょっとしたパワー・ユーザーなので、私の使っているAthlon XPプロセッサーを最大限利用するようにプログラムに対して伝えたいのです。コンパイル時にプロセッサーを最大利用することはできるのですが、ちょっとした危険性があります。最適化をオンにするということは、ビルド時間が増えるということを意味します。コンパイラーはループをたどったり定数を取り出したりといった賢明な試みをします。その結果として非常に高速なコードが得られるのですが、そのためにはビルド時間を費やすという犠牲を払うのです。

典型的なアプリケーション、OpenSSHを見てみましょう。わたしはちょうどopenssh-3.7p1.tar.gz tarballをWebサイトからダウンロードしたところで( 参考文献 にリンクがあります)、これをテスト用のアプリケーションとして使ってみます。

まずtarballを抽出します。

me@mymachine:~> tar xvzf openssh-3.7p1.tar.gz

ここで x はtarファイルを抽出します。 v は冗長出力を指定し、 ztar コマンドに対してファイルをgunzip(解凍)するように伝え、 f は抽出したいtarファイルです。a .tar.bz2ファイルの場合では、ファイルをgunzipではなくbunzipするようにtarに対して伝えるために、 z の代わりに j を使います。tarとそのオプションについて詳しく知るには、コマンドラインで man tar をタイプしてmanページを見てください。

次に、新しく生成したopensshディレクトリに移ります。

me@mymachine:~> cd openssh-3.7p1

ソースファイルをビルドするために使おうとしているツールはgccなので、gcc用のコンパイラー・オプションをいくつか指定し、私のマシンの機能を利用したいと思います。私はbashを使っているので、 export コマンドを使います(tcshなどでは setenv コマンドを使います)。

me@mymachine:~/openssh-3.7p1> export CFLAGS="-O3 -march=athlon-xp \
					
-funroll-loops -fexpensive-optimizations"
me@mymachine:~/openssh-3.7p1> export CXXFLAGS=$CFLAGS

-march フラグに注意してください。私のワークステーションはAMD Athlon XPプロセッサーを使っているので、プロセッサー専用の最適化が自動的にオンになるように、gcc 3.xにある手軽な -march=athlon-xp スイッチを使います。 -march=pentium4-march=pentiumpro を使うこともでき、また全く使わないでおくこともできます。gccのmanページを見れば、最適化のリストとそれぞれの使い方が分かります。

コンパイラーのオプションとしてはこれだけです。こうしたオプションには馴染みがないかも知れませんが、このエクスポート・コードを~/.bashrcファイルの中に置くだけで、こうしたオプションにデフォルト設定されるのは嬉しい限りです。

次に、私のマシン用にビルドを設定するために、SSHビルドにどんなオプションを含めるかを選択する必要があります。オプションの種類は次のようにタイプすることで分かります。

me@mymachine:~/openssh-3.7p1> ./configure --help

含めようと思えばこうしたオプションの一部または全てを含むこともできるのですが、とりあえずはデフォルトのままで構わないので、単に configure を単体で実行します。

me@mymachine:~/openssh-3.7p1> ./configure

さて、これで必要なのはソースコードをビルドすることだけです。これは make コマンドを使えば簡単にできます。

me@mymachine:~/openssh-3.7p1> make

これには通常しばらく時間がかかるので、一杯コーヒーでも飲もうかということになります。一旦これが終われば、私が要求したOpenSSHの全部分が揃い、得られたOpenSSHバイナリーには私がコンパイラーに対して指示した全ての最適化が含まれていることになります。ビルド時間を計ったところ、2分25秒かかっており、コーヒーの時間としては十分です。

ただし私はこの時間に不満です。私のコンピューターは2分25秒間、他のことは何も出来ずに、忙しく働いていたわけです。2分間は長い時間とは思えないかも知れませんが、OpenSSHは非常に小さなアプリケーションなのです。もっとずっと大きなプログラムの場合、あるいはコードを開発で毎日何十回もコンパイルする必要がある場合には、ビルドのために数時間も取られかねません。忙しい私としては、とてもそんな長いダウン時間を我慢する余裕はありません。そこで、短気な身を満足させるべく、distccを手に入れようというわけです。

distccでコンパイルする

distcc はちょっとしたアプリケーションで、gccコンパイラーをフックして、distccがインストールされている他のマシンでコンパイルが行えるようにします。最初のステップはdistccを自分のワークステーションにインストールすることです。最新バージョンをWebサイトからダウンロードしてください( 参考文献 にリンクがあります)。

SUSE Linuxを使っている場合はSUSEからパッケージを入手することが出来ますし、インストール用のメディアにも入っています。Gentoo Linuxでは emerge distcc を実行します。Debianでは apt-get install distcc を実行します。またお好みであればFreeBSD用に移植されたdistccもあります。

それ以外の人(または私のようにtarballが好きな人)は.tar.gzファイルを入手し、次のようにします。

me@mymachine:~/distcc-2.12.1> ./configure --with-gtk
me@mymachine:~/distcc-2.12.1> make

次にスーパーユーザーになり、次をインストールします。

me@mymachine:~/distcc-2.12.1> sudo make install

これで必要なファイルが全て設定され、distccデーモン(distccd)が/etc/init.d/distccdに落ち着いて、ブートで自動的に起動するようになるはずです。もしうまくrc.dディレクトリーにリンクされていなければ、リンクするように自分で調整します。

リブートするよりも(とにかく時間を節約したいわけですから)、とりあえず手動でデーモンを起動してみましょう。

me@mymachine:~/distcc-2.12.1> sudo /etc/init.d/distccd start

ルート・アクセス権限がなくてもdistccデーモンを実行できるという点に注意してください。これは便利です。distccデーモンはどのマシンで起動した場合でも、単にあなたのユーザー名の下で実行します。

さて、一台のマシンにdistccがあるだけでは意味がなく、利点もありません。そこで私のLAN上でLinuxを実行している3人の友人を見つけ、彼らが興味を持っているかどうかを見てみましょう。distccをインストールした人は誰でも「プール」の利点を享受できるのです。

これも注目すべき点ですが、実行しているgccのバージョンが同じである必要を除けば、各マシンに関して共通であるべき点は何もないのです。ファイルシステムやヘッダー・ファイル、あるいはライブラリを共有したりする必要はなく、同じLinuxカーネルやディストリビューションを実行する必要すらありません。

これが終わったら、どのマシンでdistccが使えるかをdistccに対して言う必要があります。それぞれのマシンを「flim」「flam」「jabberwocky」と呼ぶことにしましょう。これをもう一つのエクスポートで、今回は環境変数をDISTCC_HOSTSに設定することで行います(より恒久的に使用するために、これも~/.bashrcに置くことが出来ます)。

me@mymachine:~> export DISTCC_HOSTS="mymachine flim flam jabberwocky"

ただし私のマシンはflimやjabberwockyほど高速ではないので、リストに挙げる順番を変え、flimとjabberwockyが先に来るようにします。distccdは先に来た方が先に動作するというルールで動くようです。

me@mymachine:~> export DISTCC_HOSTS="flim jabberwocky mymachine flam"

これで全て準備できたはずです。再度我々のOpenSSHビルドを取り上げ、1台のマシンではなく3台のマシンで実行した場合にどうなるかを見てみましょう。

me@mymachine:~> cd openssh-3.7p1

CFLAGSとCXXFLAGSそれにDISTCC_HOSTSの環境変数は既にエクスポートしているので気にせずに続行すればよく、環境変数を~/.bashrcに記述しておけば記憶されるはずです。

さて、前のmakeの結果を片づけ、きれいな状態で始めましょう。

me@mymachine:~/openssh-3.7p1> make clean

始める前にもう1点。distccプログラムにはモニターがついてくるので、どのソースファイルがどのマシンでコンパイルされているのかを見ることが出来ます。distccをビルドした時に --use-gtk オプションを使ったので、モニターの選択肢としてはdistccmon-textとdistccmon-gnomeの2つがあります。とりあえずコンソール版で行きましょう。新しいターミナル・セッションを開始し、次を実行すると、2秒毎に更新されます。

me@mymachine:~/openssh-3.7p1> distccmon-text 2

あるいはこういう方法もあります(私が好きな方法です)。

me@mymachine:~/openssh-3.7p1> watch distccmon-text

このどちらでも結果は同じで、分散コンパイルのスナップショットを2秒毎に表示します。さて、これが終わったら configure を実行します。configureに対しては、通常のgccは使わないのだと分かるように、オプションを送る必要があります(私のLinuxシステムでは、何も指示がないとデフォルトでgccを使います)。

me@mymachine:~/openssh-3.7p1> CC=distcc ./configure

他のマシンで行われる設定動作の一部の影響で、distccモニターは瞬間的に止まるかも知れません。configureが完了すると、実際のコンパイルを行うことが出来ます。

me@mymachine:~/openssh-3.7p1> make -j 12

私はmakeに -j オプションを渡しています。 -j フラグはdistcc特有のものではなく、同時にいくつコンパイルするのかをgccに伝えるのです。distccを実行していないマシン上で -j オプションをつけてmakeを実行しても全く問題はありません。単一のCPUで -j を2に設定すると、スピードが上がることもあるのです(ただし劇的には上がりません)。ここでは12を指定しました。つまり、可能であれば同時に12までのソースファイルを一度にビルドするのです。

distccモニターを見て、何が行われているかを見てみましょう。

リスト1. distccコマンドライン・モニター
  5366  Preprocess  serve.c                       flim[0]
  5338  Compile     minilzo.c                     flim[1]
  5363  Preprocess  prefork.c                     flim[2]
  5360  Compile     ncpus.c                jabberwocky[0]
  5352  Compile     dparent.c              jabberwocky[1]
  5356  Compile     dsignal.c              jabberwocky[2]
  5349  Compile     dopt.c                   mymachine[0]
  5279  Compile     trace.c                  mymachine[1]
  5375  Preprocess  srvnet.c                 mymachine[2]
  5342  Compile     access.c                      flam[0]
  5346  Compile     daemon.c                      flam[1]
  5371  Preprocess  setuid.c                      flam[2]

distccモニターを使うと、どのファイルがどのノードでコンパイルされているかが分かります。右側にある、ノード名の後の数字はn番目の平行コンパイルであることを示しています。ここでは4つのノードがあり、 -j を12と規定したので、各マシンで3つのファイルがコンパイルされていることになります。これは大いに意味あるものと言えます。各ノードで必要なファイルを渡し回すとネットワークのオーバーヘッドが発生し、また各ノードに1つのコンパイラーしか無い場合には(つまり -j 4 の場合には)、各CPUはかなりの時間アイドリング状態に置かれてしまうことになるので、各マシンで複数コンパイルする意味は大きいのです。

4台のマシンで要した時間を計ってみると、コンパイルにかかった時間は9秒以下でした。これは約16倍のスピード増加になります。distccでコンパイルすると、アプリケーションを自分のワークステーション用に最適化してビルド出来るのは同じまま、自分のマシンよりもはるかに高速のノードを利用できるようになるのです。

-j の値による効果を見るために、値を変えてビルドし直してみましょう。

リスト2. 同時コンパイルの数がビルド時間に与える影響
     -j value                            build time (seconds)
     4                                           19.5
     8                                           10.5
     12                                          8.9
     16                                          8.5
     20                                          8.6

-j の値を変えることで差があることが分かるでしょう。設定を変えると結果も変わってくるので、色々な設定を試してみる価値はあります。もう一点注意しておきたいのは、distccクラスターに数台以上のマシンがある場合には、自分のローカルマシンはそのリストから外した方が無難だという点です。そうしないと自分のローカルマシンは様々なソースファイルを各マシンに渡したり、またビルドが終わったオブジェクト・ファイルを受け取ったりするのに忙しくなりすぎ、自分のコンパイルにかける時間を取られてしまうのです。実際そのマシンを残したままにしておくと、ビルド・プロセスを速度低下させる可能性があります。

最後に一点、バージョンに関しての注意です。distccプログラムは、distccクラスターの全ノードに渡ってgccのモニターのバージョンに同じものを使った場合に最大の効果を発揮します。異なったバージョンのモニターを使うと不安定なビルドになったり、時にはビルドのプロセスが完全に失敗したりすることもあります。これはバージョンが異なると、問題を引き起こすくらいgccの中の部分が異なっているためです。例えば上の例でmymachineとflimとjabberwockyがgcc 3.3.1を実行しており、flamがgcc 3.2.2を実行したとすると、どのマシンでどの部分のビルドが行われるかによって、OpenSSHのビルドは完全に成功するかも知れず、失敗するかも知れないのです。この場合では、成功したビルドであっても期待したようには動作しないかも知れないことを警告しておきます。

どのビルドも完全で安定なものとするには、同じバージョンのモニターを使うことが重要です(例えばgcc 3.3.4とgcc 3.3.1は共にgcc 3.3.xなので、この組み合わせは問題ありません)。もし何かがおかしいとしたら、おそらくdistccに関連した問題ではないはずです。

まとめ

  1. コンパイルに使用したい全てのマシンにdistccをインストールする
  2. 各マシンでdistccデーモンを開始する
  3. 各マシンの名前を付けてDISTCC_HOSTS環境変数をエクスポートする
  4. distccモニターを開始する(何が行われているかが分かるように!)
  5. ./configure では設定せず、 CC=distcc ./configure を使う
  6. makemake -j 2 でメイクせず make -j n を使う。ここでnはDISTCC_HOSTSにあるマシン数の2倍か3倍にする

最適化で効果が出るようなプログラムがある場合には、ソースコードから自分用のバイナリーを作り出すのが最善です。これは大きなビルドになると大幅に時間がかかりますが、distccを使うことによって、ネットワーク上でアイドル状態にあるCPUサイクルを有効利用できるようになり、可能な限り高速に仕上げることが出来るようになるのです。


ダウンロード可能なリソース


関連トピック

  • distcc のtarball形式のソースはSambaから、また OpenSSH はOpenSSH siteサイトからダウンロードすることが出来ます。
  • distccは GNU Cコンパイラー(gcc) で動作します。
  • Laurenceは RPM を使うよりも、tarファイルからソースのパックを解いたりソースをビルドしたりする方が好きです。 A beginner's guide to compiling programs under Linux (Linux Users of Victoria)でソースからのプログラムのコンパイルについて学んでください。この記事では、Kim OldfieldがLinuxでプログラムをコンパイルしたことのない人のために実際的な説明をしています。JC Pollmanによる Compiling Programs on Linux ( Linux Gazette, 1999)には上で説明したような内容が全て含まれており、またトラブル対策のためのヒントも含まれています( manページ を読むのも忘れないで下さい!)。
  • 最適化フラグでコンパイルすると、バイナリーがもっと早く実行するようになります。詳しくはPaul Hsieh による Programming Optimization (a zillion monkeys, 2002年) を見てください。
  • Benjaminはdistccを ccache unsermake と組み合わせて使うことを勧めています。
  • developerWorksの Linuxゾーン にはLinux開発者のための資料が豊富に用意されています。
  • Developer BookstoreのLinuxセクションでは Linux関係の書籍を値引きして購入 することが出来ます。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=228094
ArticleTitle=distccでコンパイル時間を削減する
publish-date=06222004