ビルドを高速化するsccache の紹介


はじめに

この資料は Rust 製のコマンドラインツール sscache について紹介するものです。
Rust製の環境構築やツールのインストール方法については以下を参照してください

sccache について

sccache は ccache に似たコンパイラ・キャッシングツールです。コンパイラのラッパーとして使用され、可能な限りコンパイルを回避し、ローカルディスクまたはいくつかのクラウドストレージバックエンドにキャッシュされた結果を保存します。

sccacheは、C/C++コード、Rust、およびnvccを使用したNVIDIAのCUDAのコンパイルのキャッシュもサポートしています。
また、sccacheは、Rustを含むサポートされているすべてのコンパイラーに対して、icecreamスタイルの分散コンパイル(ローカルツールチェーンの自動パッケージ化)を提供します。分散コンパイルシステムには、認証、トランスポート層の暗号化、ビルドサーバーでのサンドボックス化されたコンパイラー実行など、icecreamにないいくつかのセキュリティ機能が含まれています。

Icecream
Icecreamは、SUSE が distcc をベースに作成した分散コンパイルを行うためのプログラムです。distcc と同様に、Icecream はビルドからコンパイルジョブを受け取り、それをリモートマシンに分散し、並列ビルドを可能にします。しかし、distcc とは異なり、Icecream は中央サーバを使用し、最も高速な空きサーバに動的にコンパイルジョブをスケジューリングします。

使用方法

sccacheの実行はccacheの実行と同じで、以下のようにコンパイルコマンドの前にsccacheを付けるだけです。

sccache gcc -o foo.o -c foo.c

Rustビルドのキャッシュにsccacheを使用する場合、cargo設定ファイルに build.rustc-wrapper を定義することができます。例えば、~/.cargo/config.toml に追加することで、グローバルに設定することができます。

[build]
rustc-wrapper = "/path/to/sccache"

この機能を使用するには、cargo 1.40 以降を使用する必要があることに注意してください。
または、環境変数 RUSTC_WRAPPER を使用することもできます。

export RUSTC_WRAPPER=/path/to/sccache
cargo build

sccacheはgcc、clang、MSVC、rustc、NVCC、およびWind Riverのdiabコンパイラーをサポートしています。
特に指定しない場合、sccacheはローカルディスクキャッシュを使用します。
sccacheはクライアント・サーバーモデルを使用して動作し、サーバーはクライアントと同じマシン上でローカルに実行されます。クライアント・サーバ・モデルでは、いくつかの状態をメモリ内に保持することで、 サーバをより効率的に動作させることができます。sccache コマンドは、サーバープロセスがまだ実行されていなければ、それを起動します。また、sccache --start-server を実行すると、コンパイルを行わずにバックグラウンドのサーバープロセスを起動します。
sccache --stop-server を実行すると、サーバーを終了させることができます。また、(デフォルトでは) 10 分間何もしないと終了します。
sccache --show-stats を実行すると、キャッシュの統計情報の概要が表示されます。
sccache を Jenkins で使用する際の注意点はこちらです。
cmakeでsccacheを使うには、cmake 3.4以降で以下のコマンドライン引数を与えてください。

-DCMAKE_C_COMPILER_LAUNCHER=sccache
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache

MSVCでデバッグするためのPDBファイルを生成するために、/Z7 オプションを使用することができます。また、/Zi オプションと /Fd を併用しても、/Fd が作成されるオブジェクトファイルごとに異なるPDBファイル名を付けるのであれば、動作させることができます。なお、CMakeはデフォルトで /Zi を設定しますので、CMakeを使用する場合は、CMakeLists.txtに 以下のようなコードを追加すれば、/Z7 を使用することができます。

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
  string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
  string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
elseif(CMAKE_BUILD_TYPE STREQUAL "Release")
  string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
  string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
  string(REPLACE "/Zi" "/Z7" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
  string(REPLACE "/Zi" "/Z7" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
endif()

デフォルトでは、sccache は関連するサーバーとの通信に失敗した場合、ビルドに失敗します。代わりに sccache が停止せずにローカルのコンパイラーにフェイルオーバーするようにするには、環境変数 SCCACHE_IGNORE_SERVER_IO_ERROR=1 を設定します。

ビルド

sscache を開発目的でビルドしない場合、cargo build --release を使用して最適化されたバイナリを取得するようにしてください。

cargo build --release [--no-default-features --features=s3|redis|gcs|memcached|azure]

デフォルトでは、sccache はすべてのストレージバックエンドをサポートして構築されていますが、機能のリストをリセットし、他のすべてのバックエンドを有効にすることによって、個々のバックエンドを無効にすることができます。Cargo で機能を選択する方法の詳細については、Cargo ドキュメントを参照してください。

ポータブルバイナリのビルド

dist-server 機能でビルドする場合、sccache は OpenSSL に依存します。これは、ポータブルなバイナリを配布したい場合、厄介なことになります。openssl/vendored 機能を使って OpenSSL に対して静的にリンクすることができます。

Linux

cargoでビルドし、lddで結果のバイナリがOpenSSLに依存しなくなったことを確認する。

macOS

cargoでビルドし、otool -Lで生成されるバイナリがOpenSSLに依存しなくなったことを確認します。

Windows

Windowsでは、バイナリは、古いWindowsバージョンでは利用できないいくつかのMSVC CRT DLLに依存する場合があります。
この場合、以下の内容の .cargo/config.toml ファイルを使用して、CRT に対して静的にリンクすることができます。

[target.x86_64-pc-windows-msvc]
rustflags = ["-Ctarget-feature=+crt-static"]

cargo でビルドし、dumpbin /dependents を使って、生成されるバイナリがもう MSVC CRT DLL に依存しないことを確認します。
OpenSSLと静的にリンクする場合、$PATH に Perl が必要です。

起動時のキャッシュの分離

複数の異なるコンパイル起動が互いのキャッシュ結果を再利用しないようにするには、環境変数 SCCACHE_C_CUSTOM_CACHE_BUSTER にハッシュに混ぜられる一意の値を設定します。MACOSX_DEPLOYMENT_TARGET と IPHONEOS_DEPLOYMENT_TARGET はすでにそのような再利用抑制の動作を示しています。現在、Rustをコンパイルするためのこのような変数はありません。

キャッシュの上書き

キャッシュに壊れたビルド成果物が含まれている場合、キャッシュの内容を上書きすることが必要になることがあります。環境変数 SCCACHE_RECACHE を設定することで、これを行うことができます。

デバッグ

環境変数 SCCACHE_ERROR_LOG をパスに設定し、SCCACHE_LOG を設定して、サーバー・プロセスのログをそこにリダイレクトすることができます 。
サーバーは内部で RUST_BACKTRACE=1 を設定するので、処理されないパニックの出力も含まれます。

SCCACHE_ERROR_LOG=/tmp/sccache_log.txt SCCACHE_LOG=debug sccache

また、これらの環境変数は、例えば、ビルド・システム用に設定することもできます。

SCCACHE_ERROR_LOG=/tmp/sccache_log.txt SCCACHE_LOG=debug cmake --build /path/to/cmake/build/directory

また、ローカルでコンパイルする場合は、SCCACHE_START_SERVER=1 SCCACHE_NO_DAEMON=1 sccacheを実行してフォアグラウンドモードでサーバーを手動で実行し、環境変数 SCCACHE_LOG などを設定して標準エラーにログを送信することも可能です。この方法は、別のシェルで同時にコンパイルする必要があるため、CIサービスには不向きです。

SCCACHE_LOG=debug SCCACHE_START_SERVER=1 SCCACHE_NO_DAEMON=1 sccache

GNU make ジョブサーバとの相互作用

sccache は GNU make ジョブサーバをサポートしています。ジョブサーバを提供するプロセスからサーバが起動されると、 sccache はそのジョブサーバを使用し、起動されたプロセスにジョブサーバを提供します。(GNU make レシピから sccache を実行する場合、この挙動を得るためにコマンドの前に + を付ける必要があります)。sccache サーバーがジョブサーバーなしで起動された場合、利用可能な CPU コアの数と等しい数のスロットを持つ独自のサーバーを作成します。
これは、Rustのコンパイルにsccacheを使うときに最も便利です。rustcは並列コード生成にjobserverを使うことをサポートしているので、rustcがコード生成タスクでシステムを圧倒することがないようにします。カーゴは、rustcが使用するために、独自のジョブサーバー(Cargoのドキュメントの NUM_JOBS に関する情報を参照)を実装しているので、RUSTC_WRAPPER を介してカーゴでRustコンパイル用にsccacheを使用すると、自動的に正しい処理が行われるはずです。

既知の注意事項

一般的な注意点

  • キャッシュをヒットさせるには、ファイルの絶対パスが一致しなければなりません。つまり、共有キャッシュを使用している場合でも、お互いに利益を得るためには、全員が同じ絶対パスで (つまり $HOME 以外に) ビルドする必要があるということです。Rust では、サードパーティのクレートのソースも含まれ、デフォルトでは ~/.cargo/registry/cache に格納されます。

Rustでの注意点

  • システムリンカーを呼び出すクレートは、キャッシュできません。これには、bin、dylib、cdylib、および proc-macro クレートが含まれます。大きな bin クレートは、薄い bin ラッパーで lib create に変換することで、コンパイル時間を短縮できる場合があります。

  • インクリメンタル・コンパイルされたクレートはキャッシュできません。デフォルトでは、デバッグ プロファイルでは、Cargo はワークスペース メンバーとパス依存性のためにインクリメンタル コンパイルを使用します。インクリメンタル・コンパイルを無効にすることができます。

シンボリックリンクでの注意点

  • sccache にシンボリックリンクされているパスでは動作しません。ハードリンクを使用するようにしてください。例: ln sccache /usr/local/bin/cc・

ドキュメント

  • sccache のヘルプメッセージ

% sccache --help
sccache 0.3.3

USAGE:
    sccache [OPTIONS] <--dist-auth|--dist-status|--show-stats|--start-server|--stop-server|--zero-stats|--package-toolchain <EXE> <OUT>|CMD> [--]

ARGS:
    <CMD>...

OPTIONS:
        --dist-auth
            authenticate for distributed compilation

        --dist-status
            show status of the distributed client

    -h, --help
            Print help information

        --package-toolchain <EXE> <OUT>
            package toolchain for distributed compilation

    -s, --show-stats
            show cache statistics

        --start-server
            start background server

        --stats-format <FMT>
            set output format of statistics [default: text] [possible values:
            text, json]

        --stop-server
            stop background server

    -V, --version
            Print version information

    -z, --zero-stats
            zero statistics counters

Enabled features:
    S3:        true
    Redis:     true
    Memcached: true
    GCS:       true
    GHA:       true
    Azure:     true

ライセンス

この記事が気に入ったらサポートをしてみませんか?