次の方法で共有



年 9 月 2015

ボリューム 30 番号 9

コンパイラの最適化 - ネイティブのガイド付き最適化のプロファイルを使ったコードの効率化

Hadi Brais | 年 9 月 2015

コンパイラは、実際にはコードのパフォーマンスを改善しないどころか、実行時のパフォーマンスを低下させるような、不適切な最適化処理を行うことがよくあります。最初の 2 回で説明した最適化は、アプリのパフォーマンスにとって不可欠です。

今回は、コンパイラのバック エンドがより効率よくコードを最適化できるようにする、ガイド付き最適化のプロファイル (PGO) と呼ばれる重要な手法を取り上げます。実験結果では、パフォーマンスが 5 ~ 35% 改善しています。また、慎重に使用すれば、この手法でコードのパフォーマンスが低下することはありません。

今回は、最初の 2 回 (msdn.microsoft.com/magazine/dn904673msdn.microsoft.com/magazine/dn973015) を基に作成しています。PGO の概念をご存じない方は、最初に Visual C++ チーム ブログの投稿 (bit.ly/1fJn1DI) をお読みになることをお勧めします。

PGO の概要

コンパイラが実行する最も重要な最適化の 1 つが、関数のインライン化です。既定では、Visual C++ コンパイラは、呼び出し元のサイズが過度に大きくならない限り、関数をインライン化します。多くの場合、関数呼び出しが展開されますが、これはその関数が頻繁に呼び出される場合にしか有効ではありません。頻繁に実行されなければ、コードのサイズを増やすだけで、命令や統一キャッシュの領域を浪費し、アプリのワーキング セットが肥大します。コンパイラは呼び出しが頻繁に行われていることを、事実上、関数に渡される引数を基に判断しています。

ほとんどの最適化には、適切な判断を下すために必要な、信頼できるヒューリスティックがありません。重大なパフォーマンスの低下につながる不適切なレジスター割り当ての例を多数目にしてきました。開発者にできることは、コードのコンパイル時に、すべての最適化によるパフォーマンスの向上と低下を総合して、最終的に速度の向上が上回ることを祈るだけです。多くの場合は速度が向上しますが、過剰に大きな実行可能ファイルができあがる可能性があります。

そのような逆効果は避けたいものです。コンパイラに、コードの実行時の動作を指示できれば、コンパイラによるコードの最適化を向上できる可能性があります。プログラムの実行時の動作についての情報を記録するプロセスをプロファイリングと呼び、その結果生成される情報をプロファイルと呼びます。コンパイラに 1 つ以上のプロファイルを渡すことができます。コンパイラはそのプロファイルを基に最適化を処理します。これが、PGO 手法で行われることです。

この手法は、ネイティブ コードにもマネージ コードにも使用できます。ただし、使用するツールは異なるため、今回はネイティブ PGO のみを取り上げ、マネージ コードの PGO については別の回に説明します。ここからは、この PGO をアプリに適用する方法について説明します。

PGO は優れた手法です。ただし、他の手法と同様、欠点もあります。時間がかかり (アプリのサイズによる)、労力を要します。さいわい、後ほど説明しますが、アプリに PGO を実行する時間を大幅に短縮できるツールをマイクロソフトが用意しています。アプリへの PGO の実行には、インストルメンテーション ビルド、トレーニング、PGO ビルドの 3 つのフェーズがあります。

インストルメンテーション ビルド

実行中のプログラムをプロファイリングする方法はいくつかあります。Visual C++ コンパイラでは、静的バイナリ インストルメンテーションを使用します。これは、最も正確なプロファイルを生成できますが、時間がかかります。コンパイラは、インストルメンテーションを使用して、コード内のすべての関数の目的の場所に少数のマシン命令を挿入します (図 1 参照)。これらの命令によって、コードの関連箇所が実行されたタイミングが記録され、この情報が生成後のプロファイルに保存されます。

ガイド付き最適化のプロファイル アプリのインストルメンテーション ビルド
図 1 ガイド付き最適化のプロファイル アプリのインストルメンテーション ビルド

インストルメンテーション バージョンのアプリをビルドするには、いくつか手順があります。まず、/GL スイッチを付けてすべてのソース コード ファイルをコンパイルし、プログラム全体の最適化 (WPO) を有効にします。WPO は、プログラムのインストルメント化に必要です (技術的には不要ですが、生成後のプロファイルの有用性が大幅に向上します)。インストルメント化されるのは、/GL を使ってコンパイルされたファイルのみです。

次のフェーズをできるだけスムーズに実行できるように、コードが増えるコンパイラ スイッチは使用しません。たとえば、関数のインライン化 (/Ob0) は無効にします。また、セキュリティ チェック (/GS-) を無効にし、ランタイム チェックを外します (/RTC なし)。つまり、Visual Studio で既定のリリース モードとデバッグ モードは使用しないでください。/GL なしでコンパイルするファイルには、高速化目的の最適化 (/O2) を実行します。インストルメント化されたコードについては、少なくとも /Og を指定します。

次に、/LTCG:PGI スイッチを使って、生成後のオブジェクト ファイルと必要な静的ライブラリをリンクします。このスイッチにより、リンカーは 3 種類のタスクを実行します。このスイッチは、コードをインストルメント化し、PGO データベース (PGD) ファイルを生成するようコンパイラのバック エンドに指示します。PGD ファイルは、3 フェーズ目に使用されて、すべてのプロファイルを格納します。この時点では、PGD ファイルにはプロファイルは保持されていません。PGD ファイルの使用時にプロファイルが変更されているかどうかの検出に使うオブジェクト ファイルを指定する情報のみが含まれています。既定では、PGD ファイル名には、実行可能ファイルの名前が使用されます。オプションの /PGD リンカー スイッチを使うことで、PGD ファイルの名前を指定することもできます。3 番目のタスクは、pgort.lib インポート ライブラリをリンクすることです。出力の実行可能ファイルは、PGO ランタイム DLL pgortXXX.dll に依存します。XXX は Visual Studio のバージョンです。

このフェーズの結果として生成されるのは、インストルメンテーション コードが追加された実行可能ファイル (EXE または DLL) と、3 番目のフェーズで情報を格納し、使用する空の PGD ファイルです。インストルメント化されるプロジェクトに静的ライブラリがリンクされている場合、インストルメント化できるのは 1 つの静的ライブラリだけです。また、CIL OBJ ファイルは必ず同じバージョンのコンパイラを使用して生成する必要があります。そうしないと、リンカーからエラーが返されます。

プロファイリングのプローブ

次のフェーズに移る前に、コードのプロファイリングのためにコンパイラによって挿入されるコードについて説明しておきます。このコードにより、プログラムに追加されるオーバーヘッドを見積もり、実行時に収集される情報を把握できるようになります。

プロファイルを記録するために、/GL を指定してコンパイルされた各関数には、いくつかのプローブがコンパイラによって挿入されます。プローブは小さな命令シーケンス (2 ~ 4 個の命令) で、複数のプッシュ命令と、最後にプローブ ハンドラを呼び出す 1 つの命令で構成されます。必要に応じて、プローブは、すべての XMM レジスタを保存する関数呼び出しと復元する関数呼び出しの 2 つでラップできます。プローブには、次の 3 種類があります。

  • カウント プローブ: これは、最も一般的な種類のプローブです。特定のコード ブロックが実行されるたびにカウンタを増やし、そのコード ブロックの実行回数を数えます。これは、サイズも時間も最も少なくて済むプローブです。カウンタ 1 つのサイズは、x64 では 8 バイト、x86 では 4 バイトです。
  • エントリ プローブ: コンパイラは、エントリ プローブを各関数の先頭に挿入します。このプローブの目的は、同じ関数内の他のプローブに、その関数に関連付けられているカウンタを使用するように指示することです。これは、プローブ ハンドラをすべての関数のプローブ間で共有するために必要です。main 関数のエントリ プローブは、PGO ランタイムを初期化します。エントリ プローブも、カウント プローブの 1 種です。これは、時間が最もかかるプローブです。
  • 値プローブ: 値プローブは、各仮想関数呼び出しと Switch ステートメントの前に挿入され、値のヒストグラムの記録に使用されます。値プローブも、各値の出現回数をカウントするため、カウント プローブの 1 種です。これはサイズが最も大きくなるプローブです。

基本ブロックが 1 つ (開始と終了が 1 つだけの命令シーケンス 1 つ) しかない関数は、プローブによるインストルメント化が行われません。実際、このような関数は /Ob0 スイッチの有無にかかわらずインライン化される場合があります。各 Switch ステートメントには、値プローブの他に、そのステートメントを説明する COMDAT 定数セクションがコンパイラによって作成されます。このセクションのサイズは、おおよそ、Case の数に Switch を制御する変数のサイズを乗算した大きさになります。

どのプローブも、プローブ ハンドラへの呼び出しで終了します。main 関数のエントリ プローブは、プローブ ハンドラ ポインターのベクター (x64 では 8 バイト、x86 では 4 バイト) を作成します。このとき、各エントリはそれぞれ別のプローブ ハンドラをポイントします。ほとんどの場合、プローブ ハンドラは数個しかありません。プローブは、各関数の次の位置に挿入されます。

  • エントリ プローブ: 関数の開始部分
  • カウント プローブ: call 命令または ret 命令で終わる各基本ブロック
  • 値プローブ: 各 Switch ステートメントの直前
  • 値プローブ: 各仮想関数呼び出しの直前

インストルメント化されたプログラムのメモリ オーバーヘッドの量は、プローブ数、すべての Switch ステートメントに含まれる Case 数、Switch ステートメント数、仮想関数呼び出し数によって決まります。

ある時点ですべてのプローブ ハンドラは、カウンタを 1 つ増やし、対応するコード ブロックの実行を記録します。コンパイラは ADD 命令を使用して 4 バイトのカウンタを 1 つ増やし、x64 では ADC 命令を使って上位 4 バイトへの繰り上げを考慮します。これらの命令はスレッド セーフではありません。つまり、すべてのプローブは既定ではスレッド セーフではありません。少なくとも 1 つの関数が、同時に複数のスレッドで実行される可能性がある場合、結果は信頼できません。この場合は、/pogosafemode リンカー スイッチを使用します。このスイッチを使うと、コンパイラによってこれらの命令に LOCK プレフィックスが付けられ、すべてのプローブがスレッド セーフになります。もちろん、このために処理速度は遅くなります。残念ながら、この機能は対象を選んで適用することができません。

PGO 用の EXE ファイルや DLL ファイルが出力になる複数のプロジェクトでアプリが構成されている場合、プロジェクトごとに処理を繰り返す必要があります。

トレーニング フェーズ

最初のフェーズが完了すると、実行可能ファイルのインストルメント化バージョンと PGD ファイルが用意できています。2 番目のフェーズはトレーニングで、実行可能ファイルによって 1 つ以上のプロファイルが生成されます。このプロファイルは、別の PGO カウント (PGC) ファイルに格納されます。3 番目のフェーズでこれらのファイルを使用して、コードを最適化します。

これは、プロファイルの精度が最適化プロセス全体の成功を左右するため、最も重要なフェーズです。プロファイルを有用なものにするには、プログラムの一般的な使用シナリオを反映する必要があります。コンパイラは、実行されるシナリオが一般的であると仮定して、プログラムを最適化します。一般的なシナリオでなかった場合、プログラムの実際のパフォーマンスは低下する可能性があります。一般的な使用シナリオから生成されたプロファイルは、速度の最適化用のホット パスと、サイズの最適化用のコールド パスをコンパイラが特定するうえで役立ちます (図 2 参照)。

PGO アプリ作成のトレーニング フェーズ
図 2 PGO アプリ作成のトレーニング フェーズ

このフェーズがどの程度複雑になるかは、使用シナリオの数とプログラムの性質によって決まります。トレーニングは、プログラムにユーザー入力が必要ない場合は簡単です。使用シナリオが多数ある場合、各シナリオのプロファイルを順番に生成するのは、最速の方法にはならない可能性があります。

図 2 の複雑なトレーニング シナリオの pgosweep.exe はコマンド ライン ツールです。このツールは、実行時に PGO ランタイムによって保持されるプロファイルの内容を開発者が制御できるようにします。開発者は、プログラムのインスタンスを複数起動して、同時に使用シナリオを適用することができます。

X と Y というプロセスで 2 つのインスタンスを実行しているとします。1 つのシナリオがプロセス X で開始される直前に、pgosweep を呼び出してプロセス ID と /onlyzero スイッチを渡します。それにより、PGO ランタイムが、そのプロセスのメモリ内プロファイルだけをクリアします。プロセス ID を指定しないと、PGC プロファイル全体がクリアされます。プロファイルがクリアされると、そのシナリオが開始されます。同様にして 2 番目の使用シナリオをプロセス Y で開始できます。

PGC ファイルは、プログラムの実行中インスタンスがすべて終了した時点でのみ、生成されます。ただし、プログラムの起動に時間がかかり、シナリオごとにプログラムを実行しない場合は、1 回の実行中に、ランタイムに強制的にプロファイルを生成させて、メモリ内のプロファイルをクリアし、別のシナリオに向けてプログラムを準備できます。それには、pgosweep.exe を実行し、プロセス ID、実行可能ファイル名、PGC ファイル名を渡します。

既定では、PGC ファイルは実行可能ファイルと同じディレクトリに生成されます。これは VCPROFILE_PATH 環境変数を使用することで変更できます。その場合、プログラムの最初のインスタンスを実行する前に設定する必要があります。

インストルメント化するコードのデータと命令のオーバーヘッドについては既に説明しましたが、ほとんどの場合、このオーバーヘッドは解消できます。既定の PGO ランタイムのメモリ消費は、ある特定のしきい値を上回ることはありません。しきい値を超えるメモリが必要になった場合、エラーが発生します。その場合、VCPROFILE_ALLOC_SCALE 環境変数を使用して、しきい値を増やすことができます。

PGO ビルド

すべての一般的な使用シナリオを実行できた時点で、プログラムの最適化されたバージョンのビルドに使用できる一連の PGC ファイルが手に入ります。使用しない PGC ファイルは破棄します。

PGO バージョンをビルドするには、まず、pgomgr.exe というコマンド ライン ツールを使ってすべての PGC ファイルをマージします。このツールを使用して、PGD ファイルを編集することもできます。最初のフェーズで生成された PGD ファイルに 2 つの PGC ファイルをマージするために、/merge スイッチと PGD ファイル名を指定して pgomgr を実行します。これにより、指定した PGD ファイルの名前に !# と番号が付いた名前のカレント ディレクトリにあるすべての PGC ファイルがマージされます。コンパイラとリンカーは、マージ後の PGD ファイルを使用して、コードを最適化します。

pgomgr ツールを使用して、より一般的または重要な使用シナリオをキャプチャできます。それには、対応する PGC ファイル名と /merge:n スイッチを渡します。n は、PGD ファイルに含める PGC ファイルのコピー数を示す正の整数です。既定では、n は 1 です。このように複数のコピーを含めることで、特定のプロファイルに重みを付けて最適化を調節できます。

次は、フェーズ 1 と同じオブジェクト ファイルのセットを渡してリンカーを実行します。ただし、今度は /LTCG:PGO スイッチを使用します。リンカーは、カレント ディレクトリにある実行可能ファイルと同じ名前の PGD ファイルを探します。これにより、フェーズ 1 で PGD ファイルが生成されてから CIL OBJ ファイルが変更されていないことを確実にしたうえで、CIL OBJ ファイルをコンパイラに渡してコードを使用および最適化できます。このプロセスを図 3 に示します。/PGD リンカー スイッチを使用して、明示的に PGD ファイルを指定できます。このフェーズでは、忘れずに関数のインライン化を有効にします。

フェーズ 3 の PGO ビルド
図 3 フェーズ 3 の PGO ビルド

コンパイラとリンカーによる最適化のほとんどは、ガイド付きプロファイルになります。このフェーズの最終結果は、サイズと速度の面で高度に最適化された実行可能ファイルです。ここで、パフォーマンスがどの程度改善されたかを測定してみるのもよいでしょう。

コード ベースのメンテナンス

/LTCG:PGI スイッチを付けてリンカーに渡す入力ファイルを変更した場合、リンカーは /LTCG:PGO が指定されていると PGD ファイルの使用を拒否します。これは、そのような変更が PGD ファイルの有用性に多大な影響を与える可能性があるためです。

解決策の 1 つは、上記の 3 フェーズを繰り返し実行して、別の互換性のある PGD を生成することです。ただし、変更が少ない場合 (少数の関数の追加や関数の呼び出し頻度の多少の変更、場合によっては一般に使用されない機能の追加など)、プロセス全体を繰り返し実行することは実用的ではありません。この場合、/LTCG:PGO スイッチの代わりに /LTCG:PGU スイッチを使用します。これにより、PGD ファイルの互換性チェックを省略するように、リンカーに指示します。

時間と共に、このような少しの変更が蓄積され、いずれは、アプリを再度インストルメント化する方がよいところまで達します。このレベルに達したかどうかは、PGO のコードビルド時のコンパイラ出力を見ることで判断できます。この出力からは、PGD が対応できたコード ベースの範囲がわかります。プロファイルの対応範囲が 80% 未満になった場合 (図 4 参照)、再度コードをインストルメント化することをお勧めします。ただし、この目安になる割合は、アプリの性質に大きく左右されます。

PGO の実際の効果

PGO は、コンパイラとリンカーが採用する最適化をガイドします。今回は NBody シミュレーターを使用して、いくつか具体的なメリットを示します。このアプリは bit.ly/1gpEaCY (英語) からダウンロードできます。また、このアプリをコンパイルするために、DirectX SDK を bit.ly/1LQnKge (英語) からダウンロードしてインストールすることも必要です。

まず、リリース モードでアプリをコンパイルし、コンパイル結果と PGO バージョンとを比較します。アプリの PGO バージョンをビルドするには、Visual Studio の [ビルド] メニューの [ガイド付き最適化のプロファイル] を使用します。

/FA[c] コンパイラ スイッチを使用して (このデモでも /FA[c]s は使用しません)、アセンブラ出力も有効にします。このシンプルなアプリでは、インストルメント化したアプリを 1 度トレーニングするだけで、PGC ファイルを 1 つ生成し、これをアプリの最適化に使用できます。それにより、2 つの実行可能ファイルが作成されます。1 つはガイドなしの最適化ファイルで、もう 1 つは PGO です。後で必要になるため、作成された PGD ファイルにアクセスできることを確認します。

この 2 つの実行可能ファイルを順番に実行して、取得された GFLOP カウントを比較すると、両方とも同程度のパフォーマンスになっています。PGO をアプリに適用したのは、時間の無駄だったようです。さらに調べると、アプリのサイズが 531 KB (ガイドなしで最適化したファイルのサイズ) から 472 KB (PGO ベースのアプリのサイズ)、つまり 11% 削減されていました。したがって、PGO をこのアプリに適用すると、パフォーマンスは同じままで、サイズを削減できたことになります。これがどのように行われたかを説明します。

DXUT/Core/DXUT.CPP ファイルにある 200 行の DXUTParseCommandLine 関数を例にします。リリース ビルドで生成されたアセンブリ コードを見ると、バイナリ コードのサイズは約 2,700 バイトであることがわかります。一方、PGO ビルドのバイナリ コードのサイズは、1,650 バイト弱です。この違いが生まれた理由は、次のループ状態をチェックするアセンブリ命令からわかります。

for( int iArg = iArgStart; iArg < nNumArgs; iArg++ ) { ... }

ガイドなしで最適化されたビルドでは、次のコードが生成されました。

0x044 jge block1
; Fall-through code executed when iArg < nNumArgs
; Lots of code in between
0x362 block1:
; iArg >= nNumArgs
; Lots of other code

一方、PGO ビルドが生成したのは次のコードです。

0x043 jl   block1
; taken 0(0%), not-taken 1(100%)
block2:
; Fall-through code executed when iArg >= nNumArgs
0x05f ret  0
; Scenario dead code below
0x000 block1:
; Lots of other code executed when iArg < nNumArgs

ユーザーの多くは、コマンド ラインでパラメーターを渡すのではなく、GUI を使ってパラメーターを指定する方を好みます。そこで、ここでの一般的なシナリオでは、プロファイル情報からわかるように、ループは実行されません。プロファイルなしでは、そのことがコンパイラにはわかりません。そこで、コンパイラはループ内のコードを大胆に最適化します。多数の関数を展開するため、コードのサイズが無駄に増大します。PGO ビルドでは、ループが実行されないことを示すプロファイルがコンパイラに提供されています。このプロファイルから、コンパイラはループ本体から呼び出される関数をインライン化しても意味がないことを認識します。

アセンブリ コードのスニペットには、また別の興味深い違いがあります。ガイドなしで最適化された実行可能ファイルでは、ほとんど実行されない分岐が、条件付き命令のフォールスルー パスに含まれています。ほぼ常に実行される分岐は、条件付き命令から 800 バイト離れた位置にあります。これは、プロセッサの分岐予測が失敗する原因になりますが、命令キャッシュ ミスも確実に発生します。

PGO ビルドは、分岐の分岐位置を入れ換えることで、両方の問題を防いでいます。実際、ほとんど実行されない分岐を実行可能ファイルの別のセクションに移動することで、ワーキング セットの局所性を改善しています。この最適化は実行されないコードの分離と呼びます。これは、プロファイルなしでは実行できなかったでしょう。バイナリ コードのわずかな違いなど、あまり呼び出されない関数が、大きなパフォーマンスの違いを生むことがあります。

PGO コードのビルド時に、コンパイラはインストルメント化されたすべての関数のうち、速度の向上を目的にコンパイルされた関数の数を示します。また、この情報は、Visual Studio の [出力] ウィンドウにも表示されます。通常、速度向上を目的にコンパイルされる関数は全体の 10% 未満で (大胆なインライン化)、残りはサイズの削減を目的にコンパイルされます (部分的なインライン化またはインライン化なし)。

同じファイル内に定義されている、さらに興味深い関数、DXUTStatic­WndProc を考えてみます。関数の制御構造は、次のようになっています。

if (condition1) { /* Lots of code */ }
if (condition2) { /* Lots of code */ }
if (condition3) { /* Lots of code */ }
switch (variable) { /* Many cases with lots of code in each */ }
if-else statement
return

ガイドなしで最適化されたコードは、ソース コードと同じ順序で各コード ブロックを生成しています。しかし、PGO ビルドのコードは、各ブロックの実行頻度と、各ブロックの実行にかかった時間を使用して、効率的に再配置されています。最初の 2 つの条件はほとんど実行されないため、キャッシュとメモリの使用効率を改善するため、対応するコード ブロックは別のセクションに移動されることになります。また、ホット パスに該当すると認識されている関数 (DXUTIsWindowed など) は、インライン化されることになります。

if (condition1) { goto dead-code-section }
if (condition2) { goto dead-code-section }
if (condition3) { /* Lots of code */ }
{/* Frequently executed cases pulled outside the switch statement */}
if-else statement
return
switch(variable) { /* The rest of cases */ }

ほとんどの最適化では、信頼できるプロファイルが役立ち、プロファイルがあるからこそ実行できる最適化もあります。PGO によって目に見えるパフォーマンスの向上が得られないとしても、生成された実行可能ファイルのサイズは必ず削減され、メモリ システムのオーバーヘッドが軽減されます。

PGO データベース

PGD プロファイルには、コンパイラの最適化のガイド以外にもさまざまなメリットがあります。pgomgr.exe は複数の PGC ファイルをマージできますが、別の目的にも利用できます。pgomgr.exe には、PGD ファイルのコンテンツを表示し、実行されたシナリオについてコードの動作を完全に把握できる 3 つのスイッチが用意されています。1 つ目のスイッチは /summary で、PGD ファイルのコンテンツについてテキストで概要を出力します。2 つ目のスイッチの /detail は、/summary と併せて使用し、プロファイルの詳細なテキストによる記述を出力するよう、ツールに指示します。最後のスイッチは /unique で、関数名の修飾を外すように指示します (C++ コード ベースには特に有用です)。

プログラムによる制御

最後にもう 1 つ紹介すべき機能があります。pgobootrun.h ヘッダー ファイルは、PgoAutoSweep という関数を宣言しています。この関数をプログラムから呼び出し、PGC ファイルを生成して、メモリ内のプロファイルをクリアし、次の PGC ファイルに備えることができます。この関数は、PGC ファイル名を参照する char* 型の引数を 1 つ受け取ります。この関数を使用するには、pgobootrun.lib 静的ライブラリをリンクする必要があります。現時点では、PGO 関連のプログラミング サポートは、これだけです。

まとめ

PGO は、サイズと速度のどちらかを優先しなければならない場合に、信頼できるプロファイルを参照することで、コンパイラとリンカーによる最適化の判断の有効性を高める最適化手法の 1 つです。Visual Studio では、[ビルド] メニューやプロジェクトのコンテキスト メニューから、この手法を実行できます。

ただし、PGO プラグインを使用することで、さらに多くの機能を利用できます。PGO プラグインは、bit.ly/1Ntg4Be (英語) からダウンロードできます。このプラグインについては、https://msdn.microsoft.com/ja-jp/library/dn457343(v=vs.140).aspx でも詳しく説明されています。図 4 では対応できる範囲のしきい値を示しています。この資料で説明されている方法でプラグインを使うと、最も簡単にこのしきい値を取得できます。ただし、コマンド ライン ツールを使用する方がよい場合は、https://msdn.microsoft.com/ja-jp/library/xct6db7f(v=vs.140).aspx を参照してください。この資料には、さまざまな例があります。ネイティブのコード ベースがある場合は、すぐにこの手法を試してみることをお勧めします。実際に試してみた方は、アプリのサイズと速度にどのような効果があったかをぜひお知らせください。

PGO コード ベースのメンテナンス サイクル
図 4 PGO コード ベースのメンテナンス サイクル

関連情報

ガイド付き最適化のプロファイル データベースの詳細については、Hadi Brais のブログ記事 (bit.ly/1KBcffQ、英語) を参照してください。


Hadi Brais は、インド工科大学デリー校 (IITD) で博士号を取得した研究者で、次世代のメモリ テクノロジ向けのコンパイラ最適化について研究しています。彼は、自身の時間のほとんどを C/C++/C# のコード作成や、ランタイムとコンパイラ フレームワークの詳細な調査に費やしています。彼のブログは hadibrais.wordpress.com (英語) で公開されています。連絡先は hadi.b@live.com (英語のみ) です。

この記事のレビューに協力してくれた技術スタッフの Ankit Asthana に心より感謝いたします。