paint-brush
コンパイラーの最適化: 较大限の調整でコードのパフォーマンスを往上! に@durganshu
1,383 測定値
1,383 測定値

コンパイラーの最適化: 最小限の調整でコードのパフォーマンスを向上!

Durganshu Mishra13m2023/11/30
Read on Terminal Reader

長すぎる; 読むには

C++ コードのパフォーマンスを最適化したい開発者は、コンパイラの最適化を見つける必要があります。これは、多くの労力をかけずにコードのパフォーマンスを向上させる、非常に効果的な C++ フラグのセットです。唯一の前提条件は、自分が何をしているのかを理解していることです。 Jacobi 反復 C++ コードの実践的な対決で、-fno-alias、-xHost、-xCORE-AVX512、IPO などのインテル C++ コンパイラーに適したフラグを調べます。
featured image - コンパイラーの最適化: 最小限の調整でコードのパフォーマンスを向上!
Durganshu Mishra HackerNoon profile picture
0-item
1-item


C++ コードから较高のパフォーマンスを引き出すことは、細心のプロファイリング、複雑なメモリ アクセス調整、キャッシュの最適化を必备とする困難な作業です。これを少し簡素化するコツはありますか?幸いなことに、適切な触达があり、何をしているのかを认知していれば、较小限の労力で下跌なパフォーマンスの往上を達成する近道があります。コンパイラの最適化を導入すると、コードのパフォーマンスが下跌に往上します。


最新的のコンパイラは、特に自動並列化において、最適なパフォーマンスを目指すこの取り組みにおいて不欠な協力者として機能します。これらの洗練されたツールは、特にループ内の複雑なコード パターンを精査し、最適化をシームレスに実行する专业能力を備えています。


この記事は、人気が高く広く使用されていることで知られるインテル C++ コンパイラーに焦点を当て、コンパイラー最適化の有効性に焦点を当てることを目的としています。


このストーリーでは、思ったよりも纯手工制作業での参与を重要とせず、コードを高特点の傑作に変換できるコンパイラの魔法师の層を解き明かします。


ハイライト:コンパイラの最適化とは何ですか? | -オン |対象となるアーキテクチャ |プロシージャ間の最適化 | -fno-エイリアシング |コンパイラ最適化レポート

コンパイラの最適化とは何ですか?

コンパイラの最適化には、コンパイラがコンパイル中にソース コードに適用するさまざまな技术と変換が含まれます。しかし、なぜ?パフォーマンス、効率を向左させ、場合によっては結果として得られるマシンコードのサイズを向左させるため。これらの最適化は、进程、メモリ实的占有量、エネルギー消費など、コード実行のさまざまな側面に影響を与える上で極めて重点です。


どのコンパイラも、高レベルのソース コードを低レベルのマシン コードに変換するための一連の手順を実行します。これらには、字句概述、構文概述、后果概述、中間コード绘制 (または IR)、最適化、およびコード绘制が含まれます。


最適化フェーズでは、コンパイラーはプログラムを変換する方法を細心の注意を払って探し、より少ないリソースを使用するか、より高速に実行する意味的に同等の出力を目指します。このプロセスで使用される手法には、定数の折りたたみ、ループの最適化、関数のインライン化、デッド コードの削除などが含まれますが、これらに限定されません。


采用将会なすべてのオプションについて説明するつもりはありませんが、コードのパフォーマンスを往前させる指定の最適化を実行するようにコンパイラーに指示器する具体方法について説明します。それで、解決策は???コンパイラのフラグ。

開発者はコンパイル プロセス中にコンパイラ フラグのセットを指定できます。これは、GCC でデバッグやプロファイリング情報に「 -g」または「-pg」などのオプションを使用する人にはよく知られた手法です。先に進むにつれて、インテル C++ コンパイラーでアプリケーションをコンパイルする際に使用できる同様のコンパイラー・フラグについて説明します。これらは、コードの効率とパフォーマンスの向上に役立つ可能性があります。


キックオフに行く GIF By CAF



それで、私たちは何に取り組んでいるのでしょうか?

無味乾燥な理論を掘り下げたり、すべてのコンパイラ フラグをリストした退屈なドキュメントを多に提供数据したりするつもりはありません。代わりに、これらのフラグがなぜ、どのように機能するのかを的理解してみましょう。


どうすればこれを達成できるでしょうか?


反復の計算を担当作为する最適化されていない C++ 関数を取り上げ、各コンパイラ フラグの影響を段階的に解明していきます。この探求に沿って、最適化フラグなし (-O0) から始めて、各反復をベース バージョンと标准体系的に比較することにより、强度积极を測定します。


快速路化 (または実行時間) はマシンで測定されました。ここで、ヤコビ法は、長棱形グリッド上の熱区域划分をモデル化するための 2 次元偏微分方程组式式 (ポアソン方程组式式) を解きます。


ヤコビ法


u(x,y,t) は、時刻 t における点 (x,y) の热度です。


遍布が変化しなくなったときの安定状態を解決します。

安定状態を解く


一連のディリクレ思维的境界先决条件が思维的境界に適用されています。


核心的に、可変サイズのグリッド (解像度と呼びます) でヤコビ反復を実行する C++ コーディングがあります。核心的に、グリッド サイズ 500 は、サイズ 500x500 の行业を解くことを含意します。


1 回のヤコビ反復を実行する関数は次のとおりです。


 /* * One Jacobi iteration step */ void jacobi(double *u, double *unew, unsigned sizex, unsigned sizey) { int i, j; for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { unew[i * sizex + j] = 0.25 * (u[i * sizex + (j - 1)] + // left u[i * sizex + (j + 1)] + // right u[(i - 1) * sizex + j] + // top u[(i + 1) * sizex + j]); // bottom } } for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { u[i * sizex + j] = unew[i * sizex + j]; } } }


残差がしきい値に達するまで (ループ内で) ヤコビ反復を実行し続けます。残差の計算としきい値の評価はこの関数の静态で行われるため、ここでは関係ありません。それでは、部屋の中の象について話しましょう。

基本コードはどのように動作するのでしょうか?

最適化を行わない場合 (-O0)、次の結果が得られます。


基本ケース (「-O0」) の実行時間 (秒) および MFLOP/秒


ここでは、MFLOP/秒の観点からパフォーマンスを測定します。これが比較の基礎となります。


MFLOP/s は、「1 秒あたり 100 万回の浮動小数点演算」を表します。これは、浮動小数点演算の観点からコンピュータまたはプロセッサのパフォーマンスを参考值化するために施用される測定単位です。浮動小数点演算には、浮動小数点行驶で表現された 10 進数または実数を施用した统计学的計算が含まれます。


MFLOP/s は、特に複雑な数学3的計算が普及率している科学学および工学アプリケーションで、ベンチマークまたはパフォーマンスの指標としてよく利用されます。 MFLOP/秒の値が高いほど、システムまたはプロセッサの浮動小数点演算の実行が速くなります。


注 1:安定した結果を提供するために、解像度ごとに実行可能ファイルを 5 回実行し、MFLOP/s 値の平均値を取得します。

注 2:インテル C++ コンパイラーのデフォルトの最適化は -O2 であることに注意することが重要です。したがって、ソース コードをコンパイルするときに -O0 を指定することが重要です。


さまざまなコンパイラ フラグを試してみると、実行時間がどのように変化するかを見てみましょう。

最も一般的なもの: -O1、-O2、-O3、および -Ofast

これらは、コンパイラの最適化を開始するときに最も一般的に使用されるコンパイラ フラグの一部です。理想的なケースでは、パフォーマンスはOfast > O3 > O2 > O1 > O0 です。ただし、これは必ずしも起こるわけではありません。これらのオプションの重要な点は次のとおりです。


-O1:

  • 目標:コード サイズの増加を回避しながら速度を最適化します。
  • 主な機能:コード サイズが大きく、分岐が多く、ループ内のコードが実行時間の大半を占めていないアプリケーションに適しています。

-O2:

  • -O1 の機能強化:
    • ベクトル化を有効にします。
    • 組み込みのインライン化とファイル内のプロシージャー間の最適化が可能です。

-O3:

  • -O2 の機能強化:
    • より積極的なループ変換 (Fusion、Block-Unroll-and-Jam) が可能になります。
    • 最適化が一貫して -O2 を上回るパフォーマンスを発揮できるのは、ループとメモリ アクセスの変換が発生した場合のみです。コードの速度が遅くなる可能性もあります。
  • こんな方におすすめ:
    • ループの多い浮動小数点計算と大規模なデータ セットを使用するアプリケーション。

-オファスト:

  • 次のフラグを設定します。
    • 「-O3」
    • -no-prec-div」 : 高速で、完全な IEEE 除算よりわずかに精度が低い結果が得られる最適化を有効にします。たとえば、計算速度を向上させるために、A/B は A * (1/B) として計算されます。
    • -fp-model fast=2" : より積極的な浮動小数点最適化を有効にします。


これらのオプションがどのような最適化を作为するのかについて詳しく説明しています。


Jacobi コードでこれらのオプションを的使用すると、次の実行ランタイムが得られます。

-On フラグの比較

これらすべての最適化が、ベース コード (「-O0」を的使用) よりもはるかに髙速であることは明らかです。実行ランタイムは、大多ケースより 2 ~ 3 倍短くなります。 MFLOP/秒はどうですか??


-On フラグの比較


まあ、それは何かです!


一般ケースの MFLOP/s と最適化した場合の MFLOP/s の間には大きな違いがあります。


全体としては、わずかではありますが、「-O3」のパフォーマンスが最も優れています。


-Ofast 」(「 -no-prec-div -fp-model fast=2 」)で使用される追加のフラグは、さらなる高速化をもたらしません。

対象となるアーキテクチャ (-xHost、-xCORE-AVX512)

このマシンのアーキテクチャは、コンパイラの最適化に影響を与える極めて至关重要な环节として際立っています。コンパイラが再生利用已经な操作命令セットとハードウェアでサポートされる最適化 (ベクトル化や SIMD など) を認識している場合、パフォーマンスを大幅度的に向下させることができます。


たとえば、私の Skylake マシンには 3 つの SIMD ユニットがあります: 1 つの AVX 512 ユニットと 2 つの AVX-2 ユニット。


この知識を使って本当に何かできるでしょうか?


答えは戦略的なコンパイラ フラグにあります。 「 -xHost 」、より正確には「 -xCORE-AVX512 」などのオプションを試してみると、マシンの機能を最大限に活用し、最適なパフォーマンスを得るために最適化を調整できる可能性があります。


これらのフラグの概述を簡単に説明します。


-xホスト:

  • 目標:コンパイラーがホスト マシンの最高の命令セットに最適化されたコードを生成することを指定します。
  • 主な機能:ハードウェアで利用可能な最新の機能を利用します。これにより、ターゲット システムが驚くほど高速化されます。
  • 考慮事項:このフラグはホスト アーキテクチャ向けに最適化しますが、さまざまな命令セット アーキテクチャを備えた異なるマシン間でバイナリを移植できない可能性があります。

-xCORE-AVX512:

  • 目標:インテル アドバンスト・ベクター・エクステンション 512 (AVX-512) 命令セットを利用するコードを生成するようにコンパイラーに明示的に指示します。

  • 主な特長: AVX-512 は、AVX2 などの以前のバージョンと比較して、より幅広いベクトル レジスタと追加の演算を提供する高度な SIMD (単一命令、複数データ) 命令セットです。このフラグを有効にすると、コンパイラーはこれらの高度な機能を活用してパフォーマンスを最適化できます。

  • 考慮事項:ここでも移植性が問題です。 AVX-512 命令で生成されたバイナリは、この命令セットをサポートしていないプロセッサでは最適に動作しない可能性があります。まったく機能しない可能性があります。


AVX-512 セット系统命令は、512 ビット幅のレジスタのセットである Zmm レジスタを的使用します。これらのレジスタはベクトル処理の基礎として機能します。


デフォルトでは、「 -xCORE-AVX512 」は、プログラムが zmm レジスターの使用から恩恵を受ける可能性は低いと想定しています。コンパイラは、パフォーマンスの向上が保証されない限り、zmm レジスタの使用を回避します。


zmm レジスターを制限なしで使用する場合は、「 」を高く設定できます。私たちもそうするつもりです。


詳しい手順については必ず確認してください。


これらのフラグがコードに対してどのように機能するかを見てみましょう。

-xHost と -xCORE-AVX512 の影響

うおおお!


現在、至少解像度の 1200 MFLOP/秒のマークを超えています。他の解像度の MFLOP/s 値も増加しています。


注目すべき点は、幅度な手動介入治疗を行わずに、アプリケーションのコンパイル プロセス中にいくつかのコンパイラ フラグを組み込むだけで、これらの結果を達成したことです。


ただし、コンパイルされた実行已经ファイルは、同じ系统命令セットを用到するマシンとのみ互換性があることを強調することが至关重要です。


其他の运行命令セット用に最適化されたコードは、異なるハードウェア構成間での冻胚种植性を犠牲にする有慨率があるため、最適化と冻胚种植性のトレードオフは明らかです。だから、自分が何をしているのかを必ず知ってください!!


注:ハードウェアが AVX-512 をサポートしていなくても心配する必要はありません。インテル C++ コンパイラーは、AVX、AVX-2、さらには SSE の最適化をサポートします。 知っておくべきことがすべて記載されています。

プロシージャ間最適化 (IPO)

プロシージャ間の最適化には、個々の関数の範囲を超えて、複数の関数またはプロシージャにわたるコードの具体分析と変換が含まれます。


IPO は、プログラム内のさまざまな関数またはプロシージャ間の互为目的に聚焦を当てた複数ステップのプロセスです。 IPO には、前面置換、間接呼び出し変換、インライン化など、さまざまな種類の最適化を含めることができます。


インテル コンパイラーは、単一ファイルのコンパイルと複数ファイルのコンパイル (プログラム广大干部の最適化) という 2 つの基本的なタイプの IPO をサポートしています [ ]。それぞれを実行する 2 つの基本的なコンパイラ フラグがあります。


-ipo:

  • 目標:プロシージャ間の最適化を有効にし、コンパイラがコンパイル中に個々のソース ファイルを超えてプログラム全体を分析および最適化できるようにします。

  • 主な機能:-プログラム全体の最適化: 「 -ipo 」は、プログラム全体にわたる関数とプロシージャ間の相互作用を考慮して、すべてのソース ファイルにわたって分析と最適化を実行します。- 関数間およびモジュール間の最適化: フラグにより、関数のインライン化、同期が容易になります。最適化、およびさまざまなプログラム部分にわたるデータ フロー分析。

  • 考慮事項:別のリンク手順が必要です。 「 -ipo 」でコンパイルした後、最終的な実行可能ファイルを生成するには、特定のリンク ステップが必要です。コンパイラは、リンク中にプログラム全体のビューに基づいて追加の最適化を実行します。


-ip:

  • 目標:プロシージャ間の分析伝播を有効にし、コンパイラが別のリンク ステップを必要とせずにプロシージャ間の最適化を実行できるようにします。

  • 主な機能:-分析と伝播: 「 -ip 」を使用すると、コンパイラはコンパイル中にさまざまな関数およびモジュール間で調査とデータ伝播を実行できます。ただし、完全なプログラム ビューを必要とするすべての最適化が実行されるわけではありません。 - コンパイルの高速化: 「 -ipo 」とは異なり、「 -ip 」では別個のリンク手順が必要ないため、コンパイル時間が短縮されます。これは、迅速なフィードバックが不可欠な開発中に有益です。

  • 考慮事項:関数のインライン化など、一部の限られたプロシージャー間の最適化のみが行われます。


-ipo は般に、別個のリンク ステップを必需とするため、より広範なプロシージャ間の最適化機能を具备しますが、コンパイル時間が長くなります。 [ ] -ip は、別のリンク ステップを有需要とせずにプロシージャ間の最適化を実行する、より讯速な带替手法であり、開発およびテストのフェーズに適しています。[ ]


ここではパフォーマンスとさまざまな最適化、コンパイル時間、または実行可能ファイルのサイズについてのみ説明しているため、「 -ipo 」に焦点を当てます。

-ipo の効果

-fno-エイリアス

上記の最適化はすべて、ハードウェアをどれだけよく正确理解し、どれだけ実験するかによって異なります。しかし、それだけではありません。コンパイラーがコードをどのように認識するかを目标しようとすると、他の内在的な最適化が目标される或许性があります。


もう直接コードを見てみましょう。


 /* * One Jacobi iteration step */ void jacobi(double *u, double *unew, unsigned sizex, unsigned sizey) { int i, j; for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { unew[i * sizex + j] = 0.25 * (u[i * sizex + (j - 1)] + // left u[i * sizex + (j + 1)] + // right u[(i - 1) * sizex + j] + // top u[(i + 1) * sizex + j]); // bottom } } for (j = 1; j < sizex - 1; j++) { for (i = 1; i < sizey - 1; i++) { u[i * sizex + j] = unew[i * sizex + j]; } } }


jacobi() 関数は、パラメータとして使用するいくつかのポインタを受け取り、ネストされた for ループ内で何かを実行します。コンパイラがソース ファイル内でこの関数を認識する場合は、細心の注意を払う必要があります。


なぜ??


uを使用してunnew を計算する式には、4 つの隣接するu値の平均が含まれます。 uunnew の両方が同じ場所を指している場合はどうなりますか?これは、エイリアス化されたポインタの古典的な問題になります [ ]。


较新的のコンパイラは尤其に賢く、平安性を確保するために、エイリアシングが应该であることを依据としています。また、このようなシナリオでは、コードのセマンティクスと工作效率に影響を与える应该性のある最適化が逃避されます。


私たちの場合、 uunew は異なるメモリ場所であり、異なる値を保存することを目的としていることがわかっています。したがって、ここにエイリアスが存在しないことをコンパイラに簡単に知らせることができます。


どうやってそれを行うのでしょうか?


方法は 2 つあります。 1 つ目は C の「 」キーワードです。ただし、コードを変更する必要があります。今のところそれは望んでいません。


何か簡単なことはありますか? 「 -fno-alias 」を試してみましょう。


-fno-エイリアス:

  • 目標:プログラム内でエイリアスを想定しないようにコンパイラーに指示します。

  • 主な機能:エイリアシングがないと仮定すると、コンパイラーはコードをより自由に最適化できるため、パフォーマンスが向上する可能性があります。

  • 考慮事項:不当なエイリアスの場合、プログラムが予期しない出力を与える可能性があるため、開発者はこのフラグの使用に注意する必要があります。


詳細については、 を对比してください。


これは私たちのコードでどのように機能するのでしょうか?

-fno-alias の効果

さて、これで何かができました!!!


ここでは、以往の最適化のほぼ 3 倍という驚くべき速度化を達成しました。このブーストの背後にある绝密は何ですか?


エイリアスを想定しないようにコンパイラーに的指示することで、強力なループ最適化を任意に実行できるようになりました。


アセンブリ コード (ただし、ここでは共有しません) と生成されたコンパイル最適化レポート (以下を参照) を詳しく調べると、コンパイラがとを巧みに応用していることがわかります。これらの変換は高度に最適化されたパフォーマンスに貢献し、コード効率に対するコンパイラ ディレクティブの大きな影響を示しています。

最終的なグラフ

すべての最適化が彼此之间にどのように実行されるかは次のとおりです。


すべての最適化フラグの比較

コンパイラー最適化レポート (-qopt-report)

インテル C++ コンパイラーは、最適化を目的として行われたすべての調整を要約した最適化レポートを生成できる貴重な機能を提供します [ ]。この包括的なレポートは YAML ファイル形式で保存され、コード内でコンパイラーによって適用される最適化の詳細なリストが表示されます。詳細な説明については、「 」に関する公式ドキュメントを参照してください。

次は何?

実際に何もせずにコードのパフォーマンスを逐年に往右させることができるいくつかのコンパイラ フラグについて説明しました。唯一一个の前提首要条件は、やみくもに何もしないことです。自分が何をしているのか必ず解释してください!!


このようなコンパイラ フラグは何百もあり、この話ではそのうちのほんの十部について説明します。したがって、お好みのコンパイラの公试コンパイラ ガイド (特に最適化に関連するドキュメント) を定义する価値があります。


これらのコンパイラ フラグとは別に、ベクトル化、SIMD 組み込み、 、 など、コードのパフォーマンスを驚くほど乐观させるテクニックが而言あります。


同様に、インテル C++ コンパイラー (およびすべての一般的なコンパイラー) は、非常に優れた機能であるプラグマ ディレクティブもサポートしています。 ivdep、Parallel、simd、vector などのいくつかのプラグマについては、 で確認する価値があります。


Giphy の皆さんは以上です

推奨される読書

[1] [2] [3] [4] [5] [6] [7] [8]


のによる注目の高清写真。


でも公開されています。


바카라사이트 바카라사이트 온라인바카라