コンパイラのプロファイリング
このセクションでは、コンパイラをプロファイルし、どこで時間を費やしているかを調べる方法について説明します。
何を計測したいかに応じて、いくつかの異なるアプローチがあります。
-
PR がコンパイラのパフォーマンスを改善するか、低下させるかを確認したい場合は、 ベンチマーク実行を依頼する方法について rustc-perf の章を参照してください。
-
rustcがどこで時間を費やしているかについて中〜高レベルの概要を把握したい場合:-Z self-profileフラグと measureme ツールは、プロファイリングへのクエリベースのアプローチを提供します。 詳細については、それらのドキュメントを参照してください。
-
関数レベルのパフォーマンスデータ、または単に上記のアプローチよりも詳しい情報が欲しい場合:
-
クレートグラフのコンパイル時間を見やすく視覚的に表示したい場合は、 cargo の
--timingsフラグを使用できます。 例:cargo build --timings。CARGOFLAGS="--timings" ./x buildを使って、このフラグをコンパイラ自体に対して使用できます -
メモリ使用量をプロファイルしたい場合は、使用しているオペレーティングシステムに応じて さまざまなツールを使用できます。
- Windows の場合は、WPA ガイドを読んでください。
cargo-llvm-lines による rustc のブートストラップ時間の最適化
cargo-llvm-lines を使用すると、ジェネリック関数の すべてのインスタンス化にわたる LLVM IR の行数を数えることができます。 rustc のコンパイル時間の大部分は LLVM で費やされるため、LLVM に渡すコード量を 減らすことで rustc のコンパイルが速くなる、という考え方です。
やや独自の rustc ビルドプロセスと一緒に cargo-llvm-lines を使用するには、必要な LLVM IR を取得するために
-C save-temps を使用できます。
このオプションは、コンパイル中に作成された一時的な作業生成物を保持します。
その中には、最適化パイプラインへの入力を表す LLVM IR が含まれており、
私たちの目的に理想的です。
これは LLVM ビットコード形式で *.no-opt.bc 拡張子のファイルに保存されます。
使用例:
cargo install cargo-llvm-lines
# 通常のクレートではここで `cargo llvm-lines` を実行できますが、`x` は普通ではありません :P
# 前回の実行結果が混ざらないように、毎回実行前にクリーンします。
./x clean
env RUSTFLAGS=-Csave-temps ./x build --stage 0 compiler/rustc
# 単一クレート、例: rustc_middle。(シェルの glob サポートに依存します。)
# 未最適化の LLVM ビットコードを、cargo-llvm-lines が受け付ける人間が読める LLVM アセンブリに変換します。
for f in build/x86_64-unknown-linux-gnu/stage0-rustc/x86_64-unknown-linux-gnu/release/deps/rustc_middle-*.no-opt.bc; do
./build/x86_64-unknown-linux-gnu/llvm/bin/llvm-dis "$f"
done
cargo llvm-lines --files ./build/x86_64-unknown-linux-gnu/stage0-rustc/x86_64-unknown-linux-gnu/release/deps/rustc_middle-*.ll > llvm-lines-middle.txt
# コンパイラのすべてのクレートを指定します。
for f in build/x86_64-unknown-linux-gnu/stage0-rustc/x86_64-unknown-linux-gnu/release/deps/*.no-opt.bc; do
./build/x86_64-unknown-linux-gnu/llvm/bin/llvm-dis "$f"
done
cargo llvm-lines --files ./build/x86_64-unknown-linux-gnu/stage0-rustc/x86_64-unknown-linux-gnu/release/deps/*.ll > llvm-lines.txt
コンパイラでの出力例:
Lines Copies Function name
----- ------ -------------
45207720 (100%) 1583774 (100%) (TOTAL)
2102350 (4.7%) 146650 (9.3%) core::ptr::drop_in_place
615080 (1.4%) 8392 (0.5%) std::thread::local::LocalKey<T>::try_with
594296 (1.3%) 1780 (0.1%) hashbrown::raw::RawTable<T>::rehash_in_place
592071 (1.3%) 9691 (0.6%) core::option::Option<T>::map
528172 (1.2%) 5741 (0.4%) core::alloc::layout::Layout::array
466854 (1.0%) 8863 (0.6%) core::ptr::swap_nonoverlapping_one
412736 (0.9%) 1780 (0.1%) hashbrown::raw::RawTable<T>::resize
367776 (0.8%) 2554 (0.2%) alloc::raw_vec::RawVec<T,A>::grow_amortized
367507 (0.8%) 643 (0.0%) rustc_query_system::dep_graph::graph::DepGraph<K>::with_task_impl
355882 (0.8%) 6332 (0.4%) alloc::alloc::box_free
354556 (0.8%) 14213 (0.9%) core::ptr::write
354361 (0.8%) 3590 (0.2%) core::iter::traits::iterator::Iterator::fold
347761 (0.8%) 3873 (0.2%) rustc_middle::ty::context::tls::set_tlv
337534 (0.7%) 2377 (0.2%) alloc::raw_vec::RawVec<T,A>::allocate_in
331690 (0.7%) 3192 (0.2%) hashbrown::raw::RawTable<T>::find
328756 (0.7%) 3978 (0.3%) rustc_middle::ty::context::tls::with_context_opt
326903 (0.7%) 642 (0.0%) rustc_query_system::query::plumbing::try_execute_query
これはインクリメンタルコンパイルや ./x check では動作しないようなので、
rustc を 何度も コンパイルすることになります。
耐えられるようにするため、bootstrap.toml でいくつかの設定を変更することをお勧めします:
# 私のマシンではデバッグビルドの時間は _3 分の 1_ ですが、
# stage0 rustc より先をコンパイルすると耐えがたいほど遅くなります。
rust.optimize = false
# どうせ incremental は使えないので、少し速度を上げるために無効化します。
rust.incremental = false
# 実行はしないので、デバッグチェックをコンパイルしても意味がありません。
rust.debug = false
# 単一の codegen unit を使用すると出力は少なくなりますが、コンパイルは遅くなります。
rust.codegen-units = 0 # num_cpus
llvm-lines の出力はいくつかのオプションの影響を受けます。
rust.optimize = false にすると 2.1GB から 3.5GB に増え、codegen-units = 0 にすると 4.1GB になります。
MIR 最適化の影響は小さいです。
デフォルトの RUSTFLAGS="-Z mir-opt-level=1" と比較すると、レベル 0 では 0.3GB 増え、レベル 2 では 0.2GB 減ります。
インライン化は LLVM と GCC のコード生成バックエンドで行われており、 Cranelift のものだけが欠けています。