Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

コンパイル時間

この本は主に Rust プログラムの性能を向上させることについて扱っていますが、このセクションでは Rust プログラムのコンパイル時間を短縮することについて扱います。これは、多くの人にとって関心のある関連トピックだからです。

コンパイル時間の最小化セクションでは、ビルド設定の選択によってコンパイル時間を短縮する方法について説明しました。このセクションの残りでは、プログラムのコードを変更する必要があるコンパイル時間の短縮方法について説明します。

追加のコンパイル時間短縮テクニックについては、Corrode の包括的なリストである Tips for Faster Rust Compile Times を参照してください。

可視化

Cargo には、プログラムのコンパイルを可視化できる機能があります。次のコマンドでビルドします。

cargo build --timings

完了すると、HTML ファイルの名前が出力されます。そのファイルを Web ブラウザーで開いてください。このファイルには、プログラム内のさまざまなクレート間の依存関係を示すガントチャートが含まれています。これにより、クレートグラフにどれだけの並列性があるかが分かり、コンパイルを直列化してしまう大きなクレートを分割すべきかどうかを判断する手がかりになります。グラフの読み方の詳細については、ドキュメントを参照してください。

マクロ

一部のマクロは大量のコードを生成します。そのコードのコンパイルには時間がかかります。Rust コンパイラの -Zmacro-stats フラグは、そのようなケースを特定するのに役立ちます。

たとえば、プロジェクトのリーフクレートだけを測定したい場合は、次のようにします。

cargo +nightly rustc -- -Zmacro-stats

コンパイラは、手続き型マクロと宣言型マクロの両方によって生成されたコード量に関する情報を出力します。通常、前者の方がより注目に値します。

また、プロジェクト内のすべてのクレートを測定したい場合は、次のようにします。

RUSTFLAGS="-Zmacro-stats" cargo +nightly build

生成されたコード自体を見るには、cargo-expand を使用できます。

少量のコードしか生成しないマクロについて心配する価値はありませんが、マクロが手書きのコード量に匹敵する量のコードを生成している場合は、そのマクロの使用を完全に取り除くか、より低コストな代替手段に置き換えられる可能性があります。

あるいは、生成するコードが少なくなるようにマクロを変更できる可能性もあります。 例 1例 2

LLVM IR

Rust コンパイラはバックエンドに LLVM を使用しています。LLVM の実行はコンパイル時間の大きな部分を占めることがあり、特に Rust コンパイラのフロントエンドが大量の IR を生成し、それを LLVM が最適化するのに長い時間がかかる場合に顕著です。

これらの問題は cargo llvm-lines で診断できます。これは、どの Rust 関数が最も多くの LLVM IR を生成しているかを示します。ジェネリック関数は、大規模なプログラムでは数十回、あるいは数百回もインスタンス化されることがあるため、多くの場合で最も重要です。

ジェネリック関数が IR の肥大化を引き起こしている場合、それを修正する方法はいくつかあります。最も単純なのは、その関数を小さくすることです。 例 1例 2

別の方法は、関数の非ジェネリックな部分を、別の非ジェネリック関数に移すことです。そうすれば、その関数は一度だけインスタンス化されます。これが可能かどうかは、ジェネリック関数の詳細によって異なります。可能な場合、非ジェネリック関数は多くの場合、std::fs::read のコードで示されているように、ジェネリック関数内の内部関数としてきれいに書けます。

pub fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
    fn inner(path: &Path) -> io::Result<Vec<u8>> {
        let mut file = File::open(path)?;
        let size = file.metadata().map(|m| m.len()).unwrap_or(0);
        let mut bytes = Vec::with_capacity(size as usize);
        io::default_read_to_end(&mut file, &mut bytes)?;
        Ok(bytes)
    }
    inner(path.as_ref())
}

Option::mapResult::map_err のような一般的なユーティリティ関数が何度もインスタンス化されることがあります。これらを同等の match 式に置き換えると、コンパイル時間の短縮に役立つことがあります。

この種の変更がコンパイル時間に与える影響は通常は小さいものですが、ときには大きくなることもあります。

このような変更は、バイナリサイズの削減にもつながります。