117
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

コンパイルキャッシュでRustのビルド時間を短縮しよう

Last updated at Posted at 2019-12-01

これは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.mddocs/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/*.rsCargo.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の新しいバージョンが公開されたときは、私は以下のようにしてアップグレードしています。

私のやりかた

  1. RUSTC_WRAPPERを指定したまま(つまりsccacheを使って)cargo install -f sccacheを実行する
  2. sccacheのサーバープロセス(古いバージョン)を停止する(sccache --stop-server

このやり方が心配なら、sccacheを使わずにcargo installを実行してもいいかもしれません。

もっと慎重なやりかた

  1. sccacheのサーバープロセスを停止する(sccache --stop-server
  2. unset RUSTC_WRAPPER
  3. cargo install -f sccacheを実行する

まとめ

sccacheはローカルディスクで使うなら設定が不要でトラブルもほとんどなく、またビルドが確実に高速化するのでおすすめです。

ビルドサーバーを使う構成については、私には経験がないので何も言えません。ただ、Mozillaの各オフィスでは使っているようですので、実用レベルには達しているようです。開発者の人数が多く、ビルドに長い時間がかかるプロジェクトなら、高い効果を期待できそうです。

117
61
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
117
61

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?