これはRustその3 Advent Calendar 2019の初日の記事です。
sccache — Mozillaが開発したRust製のコンパイルキャッシュ
Rustはコンパイル言語です。個人的にはRustのコンパイル速度は遅くはない方だと思っていますが、依存しているクレートが多いとビルドにかかる時間が長くなります。特に非同期I/Oを行うWebクライアント/サーバーのクレートを使ったときや、Cargo自体をライブラリとして使うCargoサブコマンドをビルドするときなどは、依存クレートが200個くらいになることがあり、マシンの性能によってはビルドにかなりの時間を要します。
ビルド時間を短縮するためにコンパイルキャッシュという種類のソフトウェアがあります。これはコンパイルによって作られた成果物をディスクなどにキャッシュしておき、同じ条件のコンパイル要求があったときには、キャッシュしたものを返してくれるというものです。CやC++向けですと ccache が有名です。
Rustで使用できるものには、Mozillaの sccache(Shared Compilation Cache、共有コンパイルキャッシュ)があります。これはccacheに似たコンパイルキャッシュ実装で、以下のような特徴があります。
- ccacheと同様にgccやclangを使ったCやC++コンパイルに対応
-
rustcによるコンパイル にも2016年の終わりごろから対応
- 一応、3年経ったいまでも実験的というステータス?
- Rustで実装されている
- Linux、macOS、Windowsをサポート
- キャッシュの保存先としてローカルだけでなくクラウドストレージもサポート
- ローカルディスク
- Amazon S3とその互換ストレージ
- Google Cloud Storage(GCS)
- Microsoft Azure
- Redis
- Memcached
- ビルドサーバーをサポート(docs/Distributed.md、docs/DistributedQuickstart.md)
- 共有のLinuxサーバー群を使ってコンパイルし、結果をキャッシュする
- チーム開発で便利
- Linuxクライアントだけでなく、macOS、Windowsクライアントを対象としたクロスコンパイルも可能
- クライアント認証をサポート
- Mozillaの各オフィスで導入されている(出典:Mozilla Source Tree Docs)
sccacheを使ってRustプロジェクトをビルドすると、2回目以降のビルドが速くなります。ただしリンクにかかる時間は変わらないので、依存しているクレートが少ないと、あまり効果が得られないかもしれません。
sccacheについては @hhatto さんが2年前に 紹介記事 を書かれてます。
今回はその内容と被るところが多いですが、ローカルディスクを使ったシンプルな構成のみ紹介します。最初にインストール方法とRustからの使い方を説明し、そのあと、私がその構成で半年近く使った感想などを簡単に書きます。
sccacheのインストール
sccacheをインストールするには、Rustがインストール済みの環境でcargo install sccache
を実行します。
インストールできたらsccacheのヘルプを表示してみます。
$ sccache -h
sccache 0.2.12
USAGE:
sccache [FLAGS] [OPTIONS] [cmd]...
FLAGS:
--dist-auth authenticate for distributed compilation
--dist-status show status of the distributed client
-h, --help Prints help information
-s, --show-stats show cache statistics
--start-server start background server
--stop-server stop background server
-V, --version Prints version information
-z, --zero-stats zero statistics counters
OPTIONS:
--package-toolchain <executable> <out> package toolchain for distributed compilation
--stats-format <stats-format>
set output format of statistics [default: text] [possible values: text, json]
ARGS:
<cmd>...
Enabled features:
S3: true
Redis: false
Memcached: false
GCS: false
Azure: false
ローカルディスクを使うなら、sccacheの設定は不要です。
Rustから使ってみる
Rustからsccache使うにはRust 1.18からサポートされているRUSTC_WRAPPER
環境変数を使います。
$ export RUSTC_WRAPPER=$(which sccache)
あとは普通にCargoでビルドするだけです。
本当に速くなるか計ってみる
Actix-Webを使ったパッケージ(Rustプロジェクト)で試してみました。
環境は以下のとおりです。
- Rust 1.39.0
- sccache 0.2.12
- OS: Fedora 31
- CPU: Core i5-7200U @2.5GHz(低消費電力のラップトップ級CPU)
Rustパッケージ(src/*.rs
やCargo.toml
)を用意します。
# 書籍『実践Rust入門』のサンプルコードを使用する
$ git clone git@github.com:ghmagazine/rustbook.git
$ cd rustbook/ch11/templates
# 依存クレートを確認
$ tail -5 Cargo.toml
[dependencies]
actix-web = "0.7.16"
tera = "0.11.20"
serde_derive = "1"
serde = "1"
このパッケージのビルドにかかる時間を測定します。とはいえクレートをダウンロードする時間は計りたくないので、まずはsccacheなしでcargo check
を実行しました。
$ unset RUSTC_WRAPPER
$ cargo check
Downloaded serde_derive v1.0.83
Downloaded actix-web v0.7.16
Downloaded sha1 v0.6.0
...
また私の環境では、sccacheによって過去のビルドの成果物がキャッシュされているので、それを削除しました。
# 統計情報を表示して、キャッシュの場所を確認する
$ sccache --show-stats
..(中略)..
Cache location Local disk: "/home/tatsuya/.cache/sccache"
Cache size 3 GiB
Max cache size 10 GiB
# sccacheのサーバープロセスを停止する
$ sccache --stop-server
# キャッシュのディレクトリを削除する
$ rm -rf $HOME/.cache/sccache
# 統計情報を再度表示して、cache sizeがゼロになったことを確認
$ sccache --show-stats
...
Cache location Local disk: "/home/tatsuya/.cache/sccache"
Cache size 0 bytes
Max cache size 10 GiB
なおsccacheのサーバープロセスはCargoからのコンパイル要求があったときに自動でスタートします。また、最後のコンパイル要求から10分が経過したら自動的に停止します。
sccacheなしとありでビルドします。
$ pwd
/home/tatsuya/ ... /rustbook/ch11/templates
# sccacheを使わずにビルド
$ unset RUSTC_WRAPPER
$ cargo clean
$ cargo build # デバッグビルド
$ cargo clean
$ cargo build --release # リリースビルド
# sccacheを使ってビルド
$ export RUSTC_WRAPPER=$(which sccache)
$ cargo clean
$ cargo build # デバッグビルド
$ cargo clean
$ cargo build --release # リリースビルド
sccacheを使ったビルドの方はデバッグビルドとリリースビルドのそれぞれについて、3回ずつ測定しました。結果は以下のとおりです。
デバッグビルド
sccacheの有無 | ビルドの内容 | 所要時間 | sccacheなしが基準の相対速度 |
---|---|---|---|
なし | デバッグビルド | 2m 01s | 1.00 |
あり | デバッグビルド 1回目 | 2m 24s | 0.84 |
あり | デバッグビルド 2回目 | 0m 36s | 3.36 |
あり | デバッグビルド 3回目 | 0m 37s | 3.27 |
リリースビルド
sccacheの有無 | ビルドの内容 | 所要時間 | sccacheなしを基準にした相対速度 |
---|---|---|---|
なし | リリースビルド | 4m 59s | 1.00 |
あり | リリースビルド 1回目 | 5m 13s | 0.95 |
あり | リリースビルド 2回目 | 1m 11s | 4.21 |
あり | リリースビルド 3回目 | 1m 09s | 4.33 |
まとめると以下のようになります。
- なにもキャッシュされていない状態では、sccacheありのほうが、なしよりも5%から15%遅くなった
- 2回目以降のビルドでは、sccacheありのほうが、なしよりも3.3倍から4.3倍速くなった
このまま別のRustパッケージもビルドしてみます。同じバージョンのActix Webを使用しますので、1回目のビルドからキャッシュの効果を期待できます。
$ cd ../start-aw
$ tail -4 Cargo.toml
[dependencies]
actix-web = "0.7"
serde = "1"
serde_derive = "1"
リリースビルドのみ実行しました。結果は以下のとおりです。
sccacheの有無 | ビルドの内容 | 所要時間 | sccacheなしを基準にした相対速度 |
---|---|---|---|
なし | リリースビルド | 4m 18s | 1.00 |
あり | リリースビルド 1回目 | 2m 16s | 1.90 |
あり | リリースビルド 2回目 | 1m 06s | 3.90 |
あり | リリースビルド 3回目 | 1m 06s | 3.90 |
予想どおり、1回目からある程度高速化しました。
キャッシュのヒット率などの統計情報を表示してみました。
$ sccache --show-stats
Compile requests 1698
Compile requests executed 1407
Cache hits 1041
Cache hits (Rust) 1041
Cache misses 366
Cache misses (Rust) 366
Cache timeouts 0
Cache read errors 0
Forced recaches 0
Cache write errors 0
Compilation failures 0
Cache errors 0
Non-cacheable compilations 0
Non-cacheable calls 291
Non-compilation calls 0
Unsupported compiler calls 0
Average cache write 0.000 s
Average cache read miss 2.660 s
Average cache read hit 0.001 s
Failed distributed compilations 0
Non-cacheable reasons:
crate-type 264
- 27
Cache location Local disk: "/home/tatsuya/.cache/sccache"
Cache size 323 MiB
Max cache size 10 GiB
ディスクキャッシュは最大10GBまで保持する設定になっており、それを超えると、古いものから消されていきます。
半年近く使ってみた感想
ローカルディスクを使ったシンプルな構成のみで半年ほど使ってみました。
- sccache 0.2.8から0.2.11
- キャッシュの保存先はローカルディスク
- Rust 1.35.0から1.39.0までのstableと、同時期のnightly(〜1.41.0)
- OS(x86_64系)
- macOS 10.14 Mojave、10.15 Catalina
- FreeBSD 12.0-RELEASE、12.1-RELEASE
- Fedora 30、31
クラウドストレージやビルドサーバーは使っていません。というのは、私はリモート勤務なのでオフィスにいる同僚たちと離れているのと、ホームオフィスのマシンも1台ずつOSが異なるためです。
sccacheは基本的にいつもオンにしています。ただしRustコンパイラ(GitHub rust-lang/rust)をソースコードからビルドするときだけは、念のためオフにしています。
使っていて遭遇したトラブルやsccacheのアップグレード方法を紹介します。
トラブルや注意点など
sccacheに関連するトラブルは2回だけありました。ある日、nightlyツールチェインをアップデートしたら、rustc
が変なエラーで動かなくなってしまいました。最初はコンパイラがおかしいのかと疑ってましたが、実際にはrustc
のあるコマンドライン引数に変更があって、sccacheがそれに対応していなかったことが原因でした。普段はsccacheがトラブルフリーなので使っていることさえ忘れかけてしまい、少し悩みました。結局、sccacheを最新版にアップグレードしたところ、あっさり解決しました。(たしかissueも上がっていたと思います)
もう1つのトラブルは単に使い方の問題でした。上の件とはまた別のときにsccacheをアップグレードしたのですが、そのあと、sccacheのサーバープロセスを再起動することを忘れてしまいまったのです。どんなエラーだったか忘れてしまいましたが、cargo build
に失敗し、間違いに気づきました。
なおsccacheの docs/Rust.md にRustから使う際の注意点がまとめられていますので、かならず目を通すことをおすすめします。特にsccacheが管理できない方法でコンパイル結果に影響を与えるようなクレートがあると問題が起きそうです。たとえば以下のようなことが書かれています。
- Values from
env!
will not be tracked in caching. - Procedural macros that read files from the filesystem may not be cached properly
幸いにも私はこのようなことが原因となるトラブルに遭遇したことはありません。(気づいてないだけかもしれませんが)
こういう部分には少し不安がありますので、プロダクション用のビルドにはsccacheを使わない方が無難でしょう。開発とか一部のCIとかで使うのがいいと思います。
sccacheのアップグレード
crates.ioにsccacheの新しいバージョンが公開されたときは、私は以下のようにしてアップグレードしています。
私のやりかた
-
RUSTC_WRAPPER
を指定したまま(つまりsccacheを使って)cargo install -f sccache
を実行する - sccacheのサーバープロセス(古いバージョン)を停止する(
sccache --stop-server
)
このやり方が心配なら、sccacheを使わずにcargo install
を実行してもいいかもしれません。
もっと慎重なやりかた
- sccacheのサーバープロセスを停止する(
sccache --stop-server
) unset RUSTC_WRAPPER
-
cargo install -f sccache
を実行する
まとめ
sccacheはローカルディスクで使うなら設定が不要でトラブルもほとんどなく、またビルドが確実に高速化するのでおすすめです。
ビルドサーバーを使う構成については、私には経験がないので何も言えません。ただ、Mozillaの各オフィスでは使っているようですので、実用レベルには達しているようです。開発者の人数が多く、ビルドに長い時間がかかるプロジェクトなら、高い効果を期待できそうです。