この記事では、コード生成の最適化、プロシージャー間の最適化 (IPO)、インライン展開、データ・アライメント、OpenMP* と自動並列化、および浮動小数点精度のレポートについて説明します。これらのレポートには、コンパイラーによって生成されたコードの情報が示されており、「最終的な」コード変更と最適化を行うことでパフォーマンスを向上できます。
最適化レポートの生成
インテル® C/C++ コンパイラー・クラシックでは、-qopt-report[=n] (Linux* および macOS*) または /Qopt-report[:n] (Windows*) オプションを指定すると、コンパイラー・レポートが生成されます。 「n」はオプションで、レポートの詳細レベルを指定します。0 (レポートを生成しない) から 5 (最も詳細なレポート) の値を指定できます。 この記事では、-qopt-report=5 で生成されるレポート (最も詳細なレポート) について説明します。
コード生成
コンパイラー・レポートのコード生成の最適化セクションは、利用可能なハードウェア・レジスターの数、実際に使用された数、およびスピルとフィルの回数の特定に役立ちます。 図 1 の単純な C++ ベクトル加算では、図 2 に示すコンパイラーの最適化レポートが生成されます。入力引数、グローバルおよびローカル変数、レジスタースピル、およびスタックの使用に関連したレジスターの使用状況の詳細が分かります。
1 . 単純なベクトル加算の C++ コード
レジスターは CPU の ALU に最も近いメモリーであるため、レジスターの読み書きは高速です。最高のパフォーマンスを実現するには、プログラマーはルーチン内の変数をレジスターに格納する必要があります。しかし、レジスターの数には限りがあるため、コンパイラーはレジスター割り当てを最適化するコードを生成しようとします。
ルーチンが利用可能なレジスターよりも多くの変数を使用する場合、一部の変数をスタックに退避して再び使用する際にロードする必要があります。これはレジスタースピル / フィルと呼ばれます。やむを得ない場合もありますが、可能であれば、ループ・フィッションなどのループの最適化を適用することでレジスターの使用を最適化できます。コンパイラーの最適化レポートで非常に多くのスピルが報告されている場合、「高いレジスターのプレッシャー」パフォーマンス問題を示しています。その場合、 ループアンロールの回避、ループ・フィッションの使用、スカラーコードとベクトルコードの生成などにより、 レジスターのプレッシャーを最適化することが推奨されます。
2. レジスターのプレッシャーとスタックの使用状況を示すインテル® C++ コンパイラーのコード生成レポート
プロシージャー間の最適化 (IPO)
図 3 の疑似コードは、格子ボルツマン法 (LBM) アプリケーションから抜粋したものです。 図 4 と図 5 は、IPO が有効な場合と無効な場合のコンパイラーの最適化レポートです。 コンパイル単位 lbm_file1.c と lbm_file2.c には、 関数 func_grids と func2 がそれぞれ含まれています。func2 は、タイムステップ・ループで func_grids を呼び出します。
コンパイラーのコード生成とパフォーマンスを向上させる最も一般的な最適化の 1 つは関数のインライン展開ですが、これは呼び出し先 (関数定義) と呼び出し元 (呼び出し) が単一のコンパイル単位にある場合にのみ実行できます。図 3 の LBM の例には適用されません。しかし、インテル® コンパイラーでは、プロシージャー間の最適化をサポートします (-ipo (Linux*) または / Qipo (Windows*) オプション)図 4 に示すように、func_grids の呼び出しは、func2 のループのベクトル化を妨げます。IPO を使用すると、コンパイラーはインライン展開することでこのようなループ / 関数呼び出しをベクトル化できるようになります (図 5)。
3. コンパイラーによるプロシージャー間の最適化 (IPO) の適用を示すコード例
4. IPO が無効な場合の各ファイルに対するコンパイラーの最適化レポート
5. IPO が有効な場合のコンパイラーの最適化レポート – インライン展開、ベクトル化、不要なスタティック関数の排除が行われたことを示しています。 1 つのレポート (ipo_out.optrpt) のみ生成されます。
関数のインライン展開
最も一般的なコンパイラーの最適化の 1 つは、 呼び出し先の関数を呼び出し元の中にインライン展開することです。サイズが小さい関数ほど、 インライン展開される可能性が高くなります。 コンパイラーは一般にインライン展開による 「コードの膨張」 を制限しようとしますが、 図 6 に示すオプションを変更することで、 コンパイラーの保守的な判断を変更できます。
6. インライン展開に関連したコンパイラー・オプション
浮動小数点モデルと精度
科学アプリケーションでは、一般に高い計算精度を維持することが求められることがあります。これは、32 ビットの代わりに 64 ビットの浮動小数点データ型を使用することで達成できます。しかし、 可能な場合には、より低い精度のデータ型を使用すると、パフォーマンスが向上します。さらに、-fp-model precise (Linux*) または fp:precise (Windows*) オプションを使用して、数値再現性 / 一貫性に影響するコンパイラーの最適化を無効にすることができます。
図 7 のコードは、2 次元配列の 2 乗ノルムを計算します。このコードを precise 浮動小数点モデルでコンパイルすると、コンパイラーは内部ループをベクトル化できません (図 8)。より低い精度の浮動小数点モデル (デフォルト)では、レポートで提案されているように、コンパイラーはより積極的な最適化を行うことができます。
7. 単純な 2 レベルのリダクション・ループ
8. precise 浮動小数点モデルは最適化を制限
OpenMP* と自動並列化
現代のプロセッサーにおけるもう 1 つの高速化の機会は並列化です。インテル® コンパイラーは、安全に並列化できると判断した場合 (自動並列化)、または OpenMP* を使用して明示的に並列化されている場合、並列化をサポートします (図 9)。コンパイラーの最適化レポートは、OpenMP* (-qopenmp (Linux*) または /Qopenmp (Windows*)) と自動並列化 (-parallel (Linux*) または /Qpar (Windows*)) オプションが使用されると、並列化されたループを報告します (図 10)。
9. OpenMP* の並列構文とコンパイラーにより自動並列化される可能性がある領域を含むコード
10. OpenMP* と自動並列化に関するコンパイラー・レポート
まとめ
インテル® コンパイラーは、豊富な機能セット、パフォーマンスの最適化、最新の言語標準のサポートを提供します。ぜひ、最新のコンパイラーでいくつかのコンパイラー・オプションを変更して、アプリケーション・パフォーマンスの向上を体験してください。この記事および以前の記事で説明したコンパイラー・レポートは、コンパイラーの振る舞いを理解するのに役立ちます。これらの記事で紹介した例は、重要なポイントの理解を助けます。
本記事は「Parallel Universe 41 号」の「インテル® コンパイラーの最適化レポートを使用してチューニングの可能性を見つける」より転載したものです。その他「Parallel Universe」の記事はこちらからご覧いただけます。