最適化の推奨事項
更新 : 2007 年 11 月
このドキュメントでは、Visual C++ 2005 での最適化に関する推奨事項について説明します。ここでは、次のトピックについて説明します。
コンパイラとリンカのオプション
Visual C++ 2005 での変更点
使用する最適化のレベル
浮動小数点のスイッチ
最適化の declspec
最適化のプラグマ
__restrict および __assume
組み込みのサポート
例外
コンパイラとリンカのオプション
Visual C++ 2005 での変更点
Visual C++ 2005 では、現在、PGO (Profile-Guided Optimization) がサポートされています。この最適化では、アプリケーションの最適化を行う際に、インストルメント化されたバージョンのアプリケーションを過去に実行したときに収集したプロファイル データを使用します。PGO を使用すると時間がかかる場合があるため、すべての状況に適しているとは言えません。しかし、製品の最終リリース ビルドには PGO を使用することをお勧めします。詳細については、「ガイド付き最適化のプロファイル」を参照してください。
さらに、プログラム全体の最適化 (リンク時のコード生成)、および /O1 と /O2 による最適化が改良されました。通常、これらのオプションのいずれかを指定してコンパイルしたアプリケーションの処理速度は、以前のコンパイラでコンパイルした同じアプリケーションよりも速くなります。
詳細については、「/GL (プログラム全体の最適化)」および「/O1、/O2 (プログラム サイズ、実行速度)」を参照してください。
使用する最適化のレベル
可能な限り、最終リリース ビルドは、PGO を使用してコンパイルする必要があります。インストルメント化されたビルドを実行するにはインフラストラクチャが不十分であることや、シナリオにアクセスできないことが原因で、PGO を使用してビルドできない場合は、プログラム全体の最適化を使用してビルドすることをお勧めします。
/Gy スイッチも非常に便利です。このスイッチを使用すると、関数ごとに個別の COMDAT が生成されます。その結果、参照されていない COMDAT を取り除いたり COMDAT を圧縮したりでき、リンカの柔軟性が向上します。/Gy を使用した場合の唯一の問題は、ビルド時間が多少の影響を受ける可能性があることです。重要な影響を受けるわけではないので、通常は、このスイッチを使用することをお勧めします。詳細については、「/Gy (関数レベルのリンクの有効化)」を参照してください。
64 ビット環境でリンクする場合は、/OPT:REF,ICF リンカ オプションを使用することをお勧めします。また、32 ビット環境の場合は、/OPT:REF リンカ オプションを使用することをお勧めします。詳細については、「/OPT (最適化)」を参照してください。
また、デバッグ シンボルを生成することを強くお勧めします。これは、最適化されたリリース ビルドの場合も同様です。デバッグ シンボルを生成しても生成されたコードに影響はなく、必要に応じてアプリケーションを非常に簡単にデバッグできます。
浮動小数点のスイッチ
Visual C++ 2005 では、/Op コンパイラ オプションがなくなり、浮動小数点の最適化を処理する次の 4 つのコンパイラ オプションが追加されました。
/fp:precise |
既定で推奨されているオプションです。ほとんどの場合は、これを使用します。 |
/fp:fast |
パフォーマンスが最も重要視される場合 (ゲームなど) に推奨されます。このオプションを使用すると、パフォーマンスが最も高速になります。 |
/fp:strict |
浮動小数点の例外に関して厳密な対応が必要な場合や、IEEE に沿った動作が必要な場合に推奨されます。このオプションを使用すると、パフォーマンスが最も低速になります。 |
/fp:except[-] |
/fp:strict または /fp:precise とは同時に使用できますが、/fp:fast と同時に使用することはできません。 |
詳細については、「/fp (浮動小数点の動作の指定)」を参照してください。
最適化の declspec
このセクションでは、パフォーマンスを改善するためにプログラム内で使用できる、__declspec(restrict) および __declspec(noalias) という 2 つの declspec について説明します。
restrict declspec は、__declspec(restrict) void *malloc(size_t size); など、ポインタを返す関数宣言のみに適用できます。
restrict declspec は、エイリアスを使用しないポインタを返す関数で使用されます。このキーワードは、現在のプログラムで既に使用されているポインタ値を返さないため、解放済みのメモリを使用するなどの無効な操作を行う場合を除いて、C ランタイム ライブラリの malloc の実装に使用できます。
restrict declspec は、コンパイラが最適化を実行するための詳細情報をコンパイラに伝えます。コンパイラにとって最も困難なことの 1 つは、どのポインタが他のポインタのエイリアスとなっているかを判断することですが、その際、この詳細情報はコンパイラにとって非常に役立ちます。
この情報はコンパイラでは前提とされ、コンパイラが検証することはありません。プログラムでこの restrict declspec を不適切に使用すると、プログラムが不正な動作をするおそれがあります。
詳細については、「restrict」を参照してください。
また、noalias declspec も関数だけに適用され、その関数が準純粋関数であることを示します。準純粋関数は、ローカル、引数、引数の第 1 レベルの間接指定だけを参照または変更する関数です。この declspec はコンパイラでは前提とされます。準純粋関数がグローバル、またはポインタ引数の第 2 レベルの間接指定を参照する場合、コンパイラが生成するコードはアプリケーションを中断させる可能性があります。
詳細については、「noalias」を参照してください。
最適化のプラグマ
コードを最適化する際に有用なプラグマもいくつかあります。最初に、#pragma optimize について説明します。
#pragma optimize("{opt-list}", on | off)
このプラグマを使用すると、関数ごとに特定の最適化レベルを設定できます。このプラグマは、特定の関数を最適化してコンパイルするとアプリケーションがクラッシュするという特殊な状況に適しています。このプラグマを使用すると、1 つの関数の最適化を無効にできます。
#pragma optimize("", off)
int foo() {...}
#pragma optimize("", on)
詳細については、「optimize」を参照してください。
コンパイラで実行する最適化のうち、最も重要な最適化の 1 つにインライン展開があります。ここでは、インライン展開の動作を変更できるプラグマをいくつか紹介します。
#pragma inline_recursion は、アプリケーションで再帰呼び出しをインライン展開できるようにするかどうかを指定する場合に役立ちます。既定では、インライン展開は無効になっています。関数が小さく、再帰の数が少ない場合は、このプラグマを有効にできます。詳細については、「inline_recursion」を参照してください。
インライン展開の深さを制限する場合に役立つ別のプラグマとして、#pragma inline_depth があります。このプラグマは通常、プログラムまたは関数のサイズを制限する場合に役立ちます。詳細については、「inline_depth」を参照してください。
__restrict および __assume
Visual C++ 2005 には、パフォーマンスの向上に使用できるキーワードとして __restrict および __assume があります。
まず、__restrict と __declspec(restrict) が異なるものであることに注意する必要があります。この 2 つのキーワードは多少関連がありますが、意味が異なります。__restrict は、const や volatile のような型修飾子ですが、ポインタ型に対してのみ使用できます。
__restrict によって変更したポインタは、__restrict ポインタと呼びます。__restrict ポインタは、__restrict ポインタを使用してアクセスする必要があるポインタです。つまり、__restrict ポインタが指すデータには、別のポインタを使用してアクセスすることはできません。
__restrict は Visual C++ オプティマイザにとって強力なツールになりますが、慎重に使用する必要があります。誤って使用すると、オプティマイザで最適化を実行した結果としてアプリケーションが破壊されることがあります。
Visual C++ 2005 の __restrict キーワードは、以前のバージョンの /Oa スイッチに代わるものです。
__assume は、Visual C++ の複数のリリースで使用されてきましたが、Visual C++ 2005 ではさらに便利になりました。__assume を使用すると、開発者はコンパイラに対して、変数について前提を設定するように指示できます。
たとえば、__assume(a < 5); とすると、そのコード行では a 変数の値が 5 未満であることがコンパイラに伝えられます。ここでも、これはコンパイラにとっての前提になります。プログラム内のこの時点で a が実際には 6 だと、コンパイラによる最適化後のプログラムは予想どおりに動作しない可能性があります。__assume は、switch ステートメントや条件式の前で最も役立ちます。
__assume には、いくつかの制約があります。まず、このキーワードは __restrict と同様に推奨されているだけなので、コンパイラに無視される可能性があります。また、__assume は、現在、変数を定数と比較する場合にのみ機能します。assume(a < b) など、シンボルどうしの不等式には応用できません。
組み込みのサポート
組み込みとは、呼び出しに関する情報がコンパイラに組み込まれている関数呼び出しです。コンパイラはライブラリ内の関数を呼び出すのではなく、その関数のコードを出力します。組み込みのサポートは、Visual C++ 2005 で大幅に強化されました。<Installation_Directory>\VC\include にある intrin.h ヘッダー ファイルには、サポートされている 3 つのプラットフォーム (x86、x64、および Itanium) それぞれで使用できるすべての組み込みが含まれています。
組み込みにより、プログラマは、アセンブリを使用しなくても深い部分までコーディングできます。組み込みを使用することには、いくつかの利点があります。
使用しているコードの移植性が高まります。組み込みのいくつかは、複数の CPU アーキテクチャで使用できます。
コードは依然として C/C++ で記述されているため、簡単に理解できます。
コードはコンパイラの最適化による恩恵を受けることができます。コンパイラが進歩すると、組み込みのコード生成も進歩します。
詳細については、「Compiler Intrinsics」および「Benefits of Using Intrinsics」を参照してください。
例外
例外を使用すると、それによってパフォーマンスに影響があります。コンパイラによる特定の最適化を防ぐ try ブロックを使用する場合に、いくつかの制限が導入されました。x86 プラットフォームで try ブロックを使用すると、コードの実行時に追加のステータス情報を生成する必要があるために、パフォーマンスがさらに低下します。64 ビット プラットフォームでは、try ブロックによってパフォーマンスが大きく低下することはありませんが、例外がスローされると、ハンドラの検出とスタックのアンワインドの処理に負荷がかかります。
したがって、try ブロックと catch ブロックは、本当に必要である場合を除いて使用しないことをお勧めします。例外を使用する必要がある場合は、できるだけ同期例外を使用してください。詳細については、「Structured Exception Handling (C++)」を参照してください。
最後に、例外をスローするのは、例外の場合だけにしてください。通常の制御フローに例外を使用すると、パフォーマンスが低下します。