これは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 01s1.00ありデバッグビルド 1回目2m 24s0.84ありデバッグビルド 2回目0m 36s3.36ありデバッグビルド 3回目0m 37s3.27

リリースビルド

sccacheの有無ビルドの内容所要時間sccacheなしを基準にした相対速度なしリリースビルド4m 59s1.00ありリリースビルド 1回目5m 13s0.95ありリリースビルド 2回目1m 11s4.21ありリリースビルド 3回目1m 09s4.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 18s1.00ありリリースビルド 1回目2m 16s1.90ありリリースビルド 2回目1m 06s3.90ありリリースビルド 3回目1m 06s3.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を実行してもいいかもしれません。

もっと慎重なやりかた

    1. sccacheのサーバープロセスを停止する(sccache –stop-server)

 

    1. unset RUSTC_WRAPPER

cargo install -f sccacheを実行する

まとめ

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

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

广告
将在 10 秒后关闭
bannerAds