writer : Martin Brown (questions@mcslp.com), Freelance writer and consultant
 
http://www.ibm.com/developerworks/jp/linux/library/l-ccache/index.html

 

UNIXでC/C++を使ったアプリケーション開発での標準的なビルド・プロセスにおいては、gccのようなコンパイラーと、makeのように何らかのビルド・ツールを使います。makeやその他どんなCコンパイラーでも問題になるのは、Cのプリプロセッサーがヘッダー・ファイルをどう扱うかです。典型的なCソースファイルを見れば、様々なヘッダー・ファイルへの#include参照がいくつか入っています。

Cプリプロセッサー(cpp)はファイルをコンパイルする度に、こうした各ファイルを、また参照される予定のファイルをすべて構文解析し、そしてインクルードします。その内容を構文解析することによってcppは、ごく基本的な1 KBのソースファイルだったものを8 KBのソースファイルにし、やがて何十もの、時には何百ものソースファイルを取り込むのです。一般的な開発プロジェクトでは、関係のあるヘッダー・ファイルは様々なソースファイルに何度もインクルードされ、またそれぞれのヘッダー・ファイルが数多くの他のヘッダー・ファイルを参照している場合もあります。

典型的なビルドでは、makeツールは対象が最後にビルドされた後に変更されたファイルだけをコンパイルすることによって、大幅にプロセスを単純化します。例えばリスト1のディレクトリを見ると、foo.oオブジェクトは、対応するfoo.cソースファイルを最後に変更した時よりも古いことが分かります。一方bar.oはbar.cよりも新しくなっています。適切に設定されたMakefileを使えば、ソースから再コンパイルされるのはfoo.oのみになります。

makeでは変更されたソースファイルのみをコンパイルすることによって、コンパイルすべきソースファイルの数を制限しますが、それでもまだ無駄があります。プロジェクトをコンパイルする度に、アセンブラーそして最終的にマシンコードにコンパイルされるまでに、ソースファイルはcppに構文解析されます。各ファイルにとって見ると、これは毎回ヘッダー・ファイルを再構文解析することになっている可能性があります。ビルドのプロセス全体で考えると、同じヘッダー・ファイルを何度も構文解析し、プロセッサー・サイクルを浪費している可能性があります。さらに重要なことは、ビルドのプロセスが完了するまで待たせることによって、開発者の時間を浪費してしまうのです。チーム全体として見ると、複数の開発者がそのプロセスを何度も繰り返すかもしれず、しかも日によってはそれを同時に行う可能性もあることから、その影響はさらに重大です。


リスト1. ソース環境の例
                
total 808
-rw-------  1 mc  mc    5123 24 Jul 14:17 bar.c
-rw-------  1 mc  mc   39474 24 Jul 14:19 bar.o
-rw-------  1 mc  mc    7856 24 Jul 14:17 foo.c
-rw-------  1 mc  mc   28443 24 Jul 14:19 foo.o
-rwx--x--x  1 mc  mc  319742 24 Jul 14:19 foobar*
-rw-------  1 mc  mc    1045 24 Jul 14:21 foobar.h

ccacheを使う

ccache(compiler cacheの省略)ツールはコンパイルで生成された情報をキャッシュし、例えばヘッダー・ファイルのようにビルドの特定な部分のキャッシュ情報を使うことで、通常はcppで情報を構文解析するために使われるはずの時間を節約します。例えばリスト2のファイルのコンパイルについて言えば、(foobar.hが他のヘッダー・ファイルへの参照を含んでいるとすると)ccacheはincludeステートメントを、cppで構文解析した方のファイルで置き換えるのです。これほど単純なのです。ccacheは実際に内容を読み取って理解し解釈するのではなく、(それまではファイルとなるべき最終テキストであった)あとはコンパイルするだけだったものを、単にコピーするのです。


リスト2. ソースファイルの内容
                
#include "foobar.h"
void main(void)
{
}

インストール

ccacheのインストールも、それを使うのも、皆さんが想像するほど複雑なものではありません。ccacheは今までのコンパイラーの使い方には全く影響を与えることはなく、皆さんとコンパイラーの間のインターフェースとして動作するのです。ですから、必要に応じて使うか使わないかという選択ができます。ccacheをインストールするにはSambaグループから直接ソースをダウンロードするか、あるいはローカルのミラー(参考文献)からダウンロードします。そしてダウンロードしたものを解凍します。

$ bunzip2 -c ccache-2.3.tar.bz2|tar xf -

次のディレクトリに変更します。

$ cd ccache-2.3

configureを実行します。

$ ./configure

ビルドします。

$ make

そして最後にccacheをインストールします。

$ make install

これで準備完了です。

展開

先に述べた通り、ccacheは皆さんと通常のコンパイラーの間に位置することによって動作します。ですからgccを呼ぶ代わりに、gccを最初の引き数としてccacheを呼びます。例えばコマンドラインからファイルをコンパイルするには、通常次のようにします。

$ gcc foo.c

ccacheを使うには次のようにタイプします。

$ ccache gcc foo.c

こうしたファイルを一度だけコンパイルしても、特にccacheを使ってファイルをコンパイルするのが初めてであれば、コンパイル情報はまだキャッシュされていないので、何ら利点は感じないでしょう。ですから、一般的にはccacheを設定して、恒久的にメインのコンパイラーとして入れ替える方が効果的です。そのためにはCC環境変数の値を設定します。

$ export set CC='ccache gcc'

ccacheをプロジェクト・ベースでのみ使用可能にしたいのであれば、例えばPerlのようなサードパーティーのツールをコンパイルする時にのみ使用可能にしたいのであれば、環境のトリックを使うか、configureスクリプトでそのように指定するか、またはどのCコンパイラーを使うかを指定するようなコマンドを作ります。

キャッシュを制御する

ccacheはデフォルトで、カレント・ユーザーのホーム・ディレクトリ($HOME/.ccache)内のディレクトリを使ってキャッシュ情報を保持します。チーム環境では、誰もがビルド中にキャッシュ情報を使えるように、中心となる場所をキャッシュ用に使いたいでしょう。もう一つの環境変数CCACHE_DIRがキャッシュ・ディレクトリの位置を規定します。単一マシンの環境では、必要な人が誰でもアクセスできるようなディレクトリにこれを置きます。充分なメモリーがあるとして、さらに高速にするためには、tmpfsを使ってマウントされたディレクトリを使います。これによってさらに10%から25%高速化することができます。

ネットワークを経由して接続したマシンでccacheを使用するのであれば、必ず共有するディレクトリがNFSでエクスポートされ、各クライアントにマウントされるようにします。この場合でも、さらに高速化したい場合にはtmpfsファイルシステムを使うことができます。

キャッシュをさらに細かく制御できるようなオプションが他にも幾つかあります。

  • CCACHE_LOGFILE環境変数は、ログファイル(ccacheを使うと中身が入ります)の場所を定義します。
  • ccacheで-sコマンドライン・オプションを使うと、キャッシュのパフォーマンス統計が分かります(リスト3)。
  • -Mコマンドライン・オプションを使うと、キャッシュの最大サイズを設定できます。デフォルトは1 GBです。キャッシュに対する設定はキャッシュ・ディレクトリに書き込まれます。ですから別々のユーザーやグループに対して、別々の場所で別々のキャッシュ・サイズを設定することができます。
  • -Fオプションは最大ファイル数を設定しますが、一番近い16の倍数に丸められます。-Mと同様、デフォルトを変更したい時にのみ使用します。
  • -cオプションはキャッシュを整理します。ccacheは実行中に情報を更新するため、通常はこれを使うべきではありませんが、しばらく使っていなかったキャッシュ・ディレクトリを再び使用するような時には、このオプションを使うことができます。
  • -Cオプションはキャッシュを完全にクリアします。

リスト3. ccacheキャッシュの統計
                
cache hit                             44
cache miss                           152
called for link                      107
compile failed                        11
no input file                          2
files in cache                       304
cache size                           8.8 MB
max cache size                     976.6 MB

初期オプションを設定し、ディレクトリやキャッシュ・サイズを設定してしまえば、何も変更する必要はありません。定期的なメンテナンスを行う必要もありません。

ccacheとdistccを組み合わせる

皆さんは、同じくSambaグループの別のツールdistccを既にご存じかも知れません。distccを使うと、何台かのマシンにコンパイルのプロセスを分散させることができます。そのため、あたかもmakeで(-jコマンドライン・オプションを使って)複数ジョブ・オプションを使ったかのように、起こり得る同時コンパイルの数を実質的に増やすことができます。各ホストでは、ソースファイルを構文解析前の最終形式で受け付けてから(出来上がったオブジェクト・ファイルを戻す前に)ファイルをローカルでコンパイルするデーモンがあり、distccシステムはこのデーモンを使うことで動作します。

distccはソースファイル全体を分散するので、効果があるのは一つ以上のソースファイルがあるようなプロジェクトのみです。適切に使った場合、同等な新しいノードを追加する毎にビルド時間が減少する割合は、直線的な減少よりもやや少ないものになります。

distccは構文解析された状態でファイルを分散するので、Cのプリプロセス部分をスピードアップするccacheとdistccを組み合わせて、オブジェクト・コードを生成する実際のコンパイルを行うことができます。distccとccacheをこの方法で使うには、自分のホストでdistccを設定し、メインの開発マシンでdistccとccacheを設定します。

リスト4のように、プロジェクトをビルドしたいマシンで環境変数を設定します。


リスト4. ccacheとdistccを使うための環境変数
                
export set DISTCC_HOSTS='localhost atuin nautilus pteppic kernel'
export set CCACHE_DIR=/Data/Cache/CCache
export set CCACHE_PREFIX=distcc
export set CCACHE_LOGFILE=/Data/Cache/CCache.log
export set CC='ccache gcc'

環境変数は次のように定義されます。

  • DISTCC_HOSTS は作業を分散する先のホストを指定します。
  • CCACHE_DIR はccacheディレクトリの位置を指定します。
  • CCACHE_PREFIX はccacheが(プリプロセスの後)ソースをコンパイルする真のコンパイラーを呼ぶ時に使うプレフィックスを指定します。
  • CC は最初に使うCコンパイラー(ccache)の名前を設定します。

同時に実行するコンパイルの数を-jオプションで指定してmakeを実行すると、ファイルはdistccのホストの一台に分散される前に、最初にccacheで(必要な場合にはそのキャッシュを使って)構文解析されます。

distccはコンパイルのプロセスをスピードアップしますが、環境の持つ基本的な制約を変更するわけではありません。例えば、makeが操作する同時ジョブの数は、使用できるCPUの数の2倍以上にすべきではありません。例えば4台の2CPUマシンの場合、ジョブの値を16以上に設定しても、ほとんどスピードの改善は感じられないでしょう。

統計

さて、全てが設定できたところで、どのくらい差があるものかを見てみましょう。ここではPerlをビルドする一連のテストを実行しました。ccacheは構文解析されたヘッダー・ファイルをキャッシュした時に最も効果があるので、コンパイルするにはよく身の詰まったものが必要です。これは単にmakeフェーズであり、標準のconfigureを(configure.gnuを使って)実行した後に起こります。コードのコンパイルに関係したものだけではなく、全てのステージを含んでいますが、非コンパイラー操作は全体的な統計には影響しません。

先に述べた通り、ccacheの効果は最初のコンパイルでは感じられません。以前のプリプロセッサーのパスを再利用する時に、その違いが出るのです。表1に示す再コンパイル時間は、メインのPerlソース・ディレクトリにあるCソースファイルのそれぞれを、ごくわずかいじった結果に基づいています。ここでは4ノードのネットワークで同時に行われるdistccジョブの様々な値を使って、ごく普通のgccでビルドした場合、ccache+gccでビルドした場合、ccache+distcc+gccでビルドした場合の時間を計測しています。


表1. 再コンパイル時間
環境 時間
gcc (1回目)   8m02.273s
gcc (再コンパイル)   3m30.051s
ccache+gcc (1回目)   8m54.714s
ccache+gcc (再コンパイル)   0m45.455s
ccache+distcc+gcc -j4   4m14.546s
ccache+distcc+gcc -j4 (再コンパイル)   0m38.985s
ccache+distcc+gcc -j8   3m13.020s
ccache+distcc+gcc -j8 (再コンパイル)   0m34.380s

何と素晴らしい! ccacheを使うだけでPerlのビルド時間をほとんど3分(実は2分45秒ですが)の短縮です。これはすべて、ccacheが事前に構文解析されたヘッダー・ファイルを保存しておいて使うためであり、各ソースファイルに対して常にcppを再実行することがないからです。プロセスの中にdistccも取り入れれば、再コンパイル時間が少し早くなると同時に、全体的なスピードも向上します。

まとめ

この記事では、ccacheのように比較的単純で使いやすいツールを使うことによって、どれほどスピードが改善されるかを見てきました。ccacheをdistccと組み合わせて使えば、さらにコンパイル時間を短縮することができます。こうしたツールをチーム環境で使えば、コンパイル時間だけで毎日何時間も節約できることになります。開発メンバーにとってはコーヒー・ブレークの時間が少なくなることを意味するかも知れませんが、アプリケーションの開発時間も短縮できるのです。


参考文献

 

빨리 집에갈려면 익혀둬야 할 기술 

+ Recent posts