ビルド時の関数インライン化のトラブルシューティング
Build Insights の [関数] ビューを使用して、C++ プロジェクトのビルド時における関数インライン化の影響をトラブルシューティングします。
前提条件
- Visual Studio 2022 17.8 以降
- C++ Build Insights は、C++ によるデスクトップ開発ワークロードまたは C++ によるゲーム開発ワークロードのいずれかをインストールする場合、既定で有効になります。
インストールされたコンポーネントの一覧が表示されています。 C++ Build Insights が強調表示され、選択されています。これはインストール済みであることを示しています。
インストールされたコンポーネントの一覧が表示されています。 C++ Build Insights が強調表示され、選択されています。これはインストール済みであることを示しています。
概要
Build Insights は Visual Studio と統合され、特に AAA ゲームなどの大規模なプロジェクトでビルド時間を最適化できるようになりました。 Build Insights は、ビルド時の高コストなコード生成の診断を支援する [関数] ビューなどの診断を行います。 各関数のコード生成にかかる時間を表示し、__forceinline
の影響を示します。
__forceinline
ディレクティブは、サイズや複雑さに関係なく、関数をインライン化するようにコンパイラに指示します。 関数をインライン化すると、関数呼び出しのオーバーヘッドを減らすことで、実行時のパフォーマンスを向上させることができます。 このアプローチの欠点は、バイナリのサイズが増加し、ビルド時間に影響が出る可能性があることです。
最適化されたビルドの場合、コードの生成に費やされる時間は、合計ビルド時間に大きく影響します。 一般に、C++ 関数の最適化はすぐに行われます。 稀に、一部の関数が大規模で複雑なためにオプティマイザーに負荷がかかり、ビルドが大幅に遅延する場合があります。
この記事では、Build Insights の [関数] ビューを使用して、ビルド内のインライン化のボトルネックを見つける方法について説明します。
ビルド オプションの設定
__forceinline
の結果を測定するには、デバッグ ビルドは __forceinline
をインライン化しないため、リリース ビルドを使用します。デバッグ ビルドは /Ob0
コンパイラ スイッチを使用するため、その最適化は無効になります。 リリース と x64 のビルドを設定します。
- [ソリューション構成] ドロップダウンで、[リリース] を選択します。
- [ソリューション プラットフォーム] ドロップダウンで、[x64] を選択します。
最適化レベルを最大最適化に設定します。
ソリューション エクスプローラーでプロジェクト名を右クリックし、[プロパティ] を選択します.
プロジェクトのプロパティで、 [C/C++]>[最適化] に移動します。
[最適化] ドロップダウンを [最大最適化 (速度優先) (
/O2
)] に設定します。[OK] をクリックしてダイアログ ボックスを閉じます。
Build Insights の実行
選択したプロジェクトで、前のセクションで設定した [リリース] ビルド オプションを使用して、メイン メニューから[ビルド]>[選択時に Build Insights を実行]>[リビルド] の順に選択して Build Insights を実行します。 ソリューション エクスプローラーでプロジェクトを右クリックし、[Build Insights の実行] >[リビルド] を選択することもできます。 現在好ましくない可能性のある少数のファイルのみではなく、プロジェクト全体のビルド時間を測定するのに [ビルド] ではなく、[リビルド] を選択します。
ビルドが完了すると、イベント トレース ログ (ETL) ファイルが開きます。 Windows TEMP
環境変数が指すフォルダーに保存されます。 生成された名前は、収集時刻に基づいています。
関数ビュー
ETL ファイルのウィンドウで、[関数] タブを選択します。コンパイルされた関数と、各関数のコード生成にかかった時間が表示されます。 関数に対して生成されるコードの量がごくわずかである場合、ビルド イベント収集のパフォーマンス低下を防ぐために、一覧には表示されません。
[関数名] 列で、performPhysicsCalculations() が強調表示され、ファイア アイコンでマークされています。
[時間 [秒, %]] 列には、ウォール クロック責任時間 (WCTR) で、各関数のコンパイルにかかった時間が表示されます。 このメトリックは、並列コンパイラ スレッドの使用に基づいて、関数間でウォール クロック時間を分散します。 たとえば、2 つの異なるスレッドが 1 秒間に 2 つの異なる関数を同時にコンパイルしている場合、各関数の WCTR は 0.5 秒として記録されます。 これは、並列実行中にそれぞれ消費されたリソースを考慮に入れて、合計コンパイル時間に対する各関数の比例配分を反映したものです。 このため、WCTR では、複数のコンパイル アクティビティが同時に発生する環境で、各ファイルが全体的なビルド時間に与える影響がより適切に測定されます。
Forceinline Size 列には、関数に対して生成された命令のおおよその数が表示されます。 関数名の前にあるシェブロンをクリックすると、その関数内でインライン化された個々の関数および、それぞれに対して生成された命令のおおよその数が表示されます。
Time 列をクリックしてソートすると、コンパイルに最も時間がかかっている関数を確認できます。 "炎" アイコンは、その関数を生成するコストが高く、調査する価値があることを示します。 __forceinline
関数を過剰に使用すると、コンパイルが大幅に遅くなる場合があります。
[フィルター関数] ボックスを使用すると、特定の関数を検索できます。 関数のコード生成時間が短すぎると、[関数] ビューには表示されません。
関数のインライン化を調整してビルド時間を短縮する
この例では、performPhysicsCalculations
関数のコンパイルに最も時間がかかっています。
[関数名] 列で、performPhysicsCalculations() が強調表示され、炎アイコンでマークされています。
さらに調査するには、その関数の前にシェブロンを選択し、Forceinline Size 列を最上位から最下位に並べ替えると、問題の最大の要因が表示されます。
performPhysicsCalculations() が展開され、その内部にインライン化された関数の長い一覧が表示されます。 complexOperation()、recursiveHelper()、sin() などの関数の複数のインスタンスが表示されています。 Forceinline Size 列は、complexOperation() には 315 件の命令があり、最大のインライン関数であることを示しています。 recursiveHelper() には 119 件の命令があります。 Sin() には 75 件の命令がありますが、他の関数よりも多くのインスタンスがあります。
問題の原因になっている Vector2D<float>::complexOperation()
や Vector2D<float>::recursiveHelper()
など、より大きなインライン関数がいくつかあります。 ただし、Vector2d<float>::sin(float)
、Vector2d<float>::cos(float)
、Vector2D<float>::power(float,int)
、Vector2D<float>::factorial(int)
(ここではすべては表示されていません) など、他にも多数のインスタンスがあります。 それらを合わせると、生成された命令の合計数は、大規模な、少数の生成された関数をすぐに超えます。
ソース コードでこれらの関数を見ると、ループ内で実行時間がかかることがわかります。 たとえば、factorial()
のコードを次に示します。
static __forceinline T factorial(int n)
{
T result = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
result *= (i - j) / (T)(j + 1);
}
}
return result;
}
この関数を呼び出す全体的なコストは、関数自体のコストと比較すると軽微である可能性があります。 関数をインラインにすることは、関数の呼び出しにかかる時間 (スタックへ引数をプッシュし、関数へジャンプし、戻り引数をポップし、関数から戻る) が、関数の実行にかかる時間とほぼ同じで、関数が頻繁に呼び出される場合、特に有益です。 そうでない場合は、インラインにすることで利点を得られなくなる可能性があります。 __forceinline
ディレクティブを削除すると、ビルド時間を改善できるかどうかを確認できます。 power
、sin()
、cos()
のコードは、コードが何度も実行されるループで構成される点で似ています。 これらの関数から __forceinline
ディレクティブを削除することもできます。
メイン メニューで [ビルド]>[選択時に Build Insights を実行]>[リビルド] を選択し、Build Insights を再実行します。 ソリューション エクスプローラーでプロジェクトを右クリックし、[Build Insights の実行] >[リビルド] を選択することもできます。 前と同じように、現在好ましくない可能性のある少数のファイルのみではなく、プロジェクト全体のビルド時間を測定するのに [ビルド] ではなく、[リビルド] を選択します。
ビルド時間は 25.181 秒から 13.376 秒になり、カウントされるビルド時間に含まれないほど軽微となるため、performPhysicsCalculations
関数は [関数] ビューに表示されなくなります。
[関数名] 列で、performPhysicsCalculations() が強調表示され、ファイア アイコンでマークされています。
診断セッション時間は、ビルドにかかった全体的な時間と、Build Insights データを収集するためのオーバーヘッドの合計です。
次の手順では、アプリケーションをプロファイリングして、変更によってアプリケーションのパフォーマンスが悪影響を受けるかどうかを確認します。 その場合は、必要に応じて __forceinline
を選択的に再度追加することができます。
ソース コードに移動する
[関数] ビューのファイル上で、ダブルクリックまたは右クリックするか、Enter キー を押すと、そのファイルのソース コードが開きます。
ヒント
- [ファイル]>[名前を付けて保存] で ETL ファイルを永続的な場所に保存すると、ビルド時間の記録を保持できます。 その後、それを将来のビルドと比較すると、変更によってビルド時間が改善されているかどうかを確認できます。
- [Build Insights] ウィンドウを誤って閉じた場合は、一時フォルダー内の
<dateandtime>.etl
ファイルを見つけて再度開きます。TEMP
Windows 環境変数で、一時ファイル フォルダーのパスが指定されています。 - Windows パフォーマンス アナライザー (WPA) を使用して Build Insights データを調べるには、ETL ウィンドウの右下にある [WPA で開く] ボタンをクリックします。
- 列をドラッグして、列の順序を変更します。 たとえば、[時間] 列を最初の列に移動することもできます。 列ヘッダーを右クリックし、表示しない列の選択を解除することで、列を非表示にすることができます。
- [関数] ビューには、目的の関数を検索するためのフィルター ボックスがあります。 指定した名前を部分一致で照会します。
- [関数] ビューの表示内容の意味を忘れた場合は、タブの上にマウス ポインターを置くと、ビューの説明がヒントで表示されます。 [関数] タブにカーソルを合わせると、"子ノードが強制インライン関数である関数の統計情報を表示するビュー" というツールチップが表示されます。
トラブルシューティング
- [Build Insights] ウィンドウが表示されない場合は、ビルドではなくリビルドを実行します。 実際に何もビルドされていない場合、[Build Insights] ウィンドウは表示されません。これは、前回のビルド以降にファイルが変更されていない場合に該当します。
- [関数] ビューに関数が表示されない場合は、適切な最適化設定を使用してビルドしていない可能性があります。 「ビルド オプションの設定」通りに、完全な最適化を行ってリリースをビルドしていることを確認します。 また、関数のコード生成時間が短すぎる場合は、[関数] ビューには表示されません。
関連項目
分析情報のヒントとテクニックを構築する
インライン関数 (C++)
C++ ビルドの高速化、簡略化: 時間の新しいメトリック
Visual Studio での Build Insights のビデオ - Pure Virtual C++ 2023
ビルド時のヘッダー ファイルの影響のトラブルシューティング
Visual Studio 2022 17.8 での Build Insights の [関数] ビュー
チュートリアル: vcperf および Windows パフォーマンス アナライザー
C++ Build Insights を使用したコード生成を高速化する