1. ホーム
  2. c++

[解決済み] O3/Ofastを超えるG++の最適化

2023-04-27 01:24:09

質問

問題点

あるシミュレーションタスクの中規模プログラムがあり、それを最適化する必要があります。私たちはすでに、プログラミング スキルの限界までソースを最適化することに全力を尽くしてきました。 Gprof Valgrind .

最終的に完成したら、おそらく数カ月間、いくつかのシステムでプログラムを実行したいと思います。したがって、私たちは、最適化を限界まで推し進めることに本当に興味があります。

すべてのシステムは、比較的新しいハードウェア (Intel i5 または i7) 上で Debian/Linux を実行する予定です。

質問事項

g++ の最近のバージョンを使用して、-O3/-Ofast を超える最適化オプションは何が可能ですか?

私たちはまた、長期的に利益をもたらす、コストのかかるマイナーな最適化に興味があります。


強いて言えば 今使っているもの

現在、以下のg++の最適化オプションを使用しています。

  • -Ofast : 最高水準の最適化レベル。含まれる -ffast-math は、私たちの計算では何の問題も引き起こさないので、標準に準拠していないにもかかわらず、それを採用することにしました。
  • -march=native : すべての CPU 固有の命令を使用できるようにします。
  • -flto を使用して、異なるコンパイル単位にまたがるリンク時の最適化を可能にします。

どのように解決するのですか?

ほとんどの回答は、別のコンパイラや外部ライブラリなど、おそらく多くの書き換えや統合作業をもたらすであろう代替の解決策を提案しています。私は、質問の内容に固執し、OP が要求したように、コンパイラー フラグを有効にするか、コードに最小限の変更を行うことによって、GCC 単独で何ができるかに焦点を当てようとします。これは、「こうしなければならない」という回答ではなく、私自身にとってうまくいったGCCの微調整のコレクションであり、あなたの特定のコンテキストに関連するものであれば、試してみることができるものです。


元の質問に関する警告

詳細に入る前に、質問に関するいくつかの警告があります。典型的には、質問を読んで、「OPはO3を超えて最適化している。

  • -march=native 命令を使用できるようになります。 を使用することができます。異なる CPU を持つシステムで実行すると、プログラムが全く動作しないか、著しく遅くなる可能性があります(これは mtune=native も有効になるため)、使用する場合はこの点に注意してください。詳細情報 はこちら .
  • -Ofast のように、いくつかの 非標準準拠の を最適化することができますので、使用には注意が必要です。詳細はこちら はこちら .

試してみたい他のGCCフラグ

異なるフラグの詳細は以下のとおりです。 はこちら .

  • -Ofast 可能にする -ffast-math を有効にし、その結果 -fno-math-errno , -funsafe-math-optimizations , -ffinite-math-only , -fno-rounding-math , -fno-signaling-nans-fcx-limited-range . さらに進んで 浮動小数点演算の最適化 を選択的に追加することで 追加フラグ 例えば -fno-signed-zeros , -fno-trapping-math などがあります。これらは -Ofast に含まれておらず、計算のパフォーマンスをさらに向上させることができますが、実際に恩恵があるかどうか、計算を壊さないかどうかをチェックする必要があります。
  • GCCはまた、大量の 他の最適化フラグ これらはどの "-O" オプションでも有効になりません。それらは "壊れたコードを生成する可能性のある実験的なオプションとしてリストされています。とはいえ、私はしばしば -frename-registers このオプションは私にとって望ましくない結果をもたらしたことはなく、顕著な性能向上(つまり、ベンチマークで測定可能)をもたらす傾向があります。これは、プロセッサに大きく依存するタイプのフラグですが。 -funroll-loops も良い結果をもたらすことがあります(また、暗黙のうちに -frename-registers を意味します) が、それは実際のコードに依存します。

PGO

GCCは プロファイル誘導型最適化 機能を備えています。これに関する正確なGCCドキュメントはあまりありませんが、それでもこれを実行するのは非常に簡単です。

  • であなたのプログラムをコンパイルします。 -fprofile-generate .
  • を実行します (コードはプロファイル情報を .gcda ファイルに生成しているため、実行時間は大幅に遅くなります)。
  • を使用してプログラムを再コンパイルします。 -fprofile-use . アプリケーションがマルチスレッドである場合は、コンパイル時に -fprofile-correction フラグを追加します。

GCC での PGO は驚くべき結果をもたらし、本当に大幅にパフォーマンスを向上させます (私が最近取り組んでいたプロジェクトの 1 つでは、15 ~ 20% の速度向上が見られました)。明らかに、ここでの問題は、いくつかの データを十分に代表する これは、常に利用可能とは限りませんし、入手するのも簡単ではありません。

GCCの並列モード

GCCの特徴は 並列モード これは、GCC 4.2 コンパイラがリリースされた頃に初めてリリースされました。

基本的に、これは以下のものを提供します。 C++ 標準ライブラリにある多くのアルゴリズムの並列実装を提供します。 . これらをグローバルに有効にするには、単に -fopenmp-D_GLIBCXX_PARALLEL フラグをコンパイラに渡します。また、必要に応じて各アルゴリズムを選択的に有効にすることもできますが、その場合は若干のコード変更が必要になります。

この並列モードに関するすべての情報は、以下のサイトで見ることができます。 を参照してください。 .

大きなデータ構造でこれらのアルゴリズムを頻繁に使用し、多くのハードウェアスレッドコンテキストが利用できる場合、これらの並列実装は大きなパフォーマンスブーストを与えることができます。私が利用した並列実装は sort の並列実装しか使用していませんが、おおよその見当をつけるために、私のアプリケーションの 1 つでソートの時間を 14 秒から 4 秒に減らすことができました (テスト環境: カスタム コンパレータ関数と 8 コアのマシンによる 1 億個のオブジェクトのベクトル)。

その他のトリック

これまでのポイント編とは異なり、このパートでは はコードの小さな変更を必要とします。 . また、それらはGCCに特有なので(いくつかはClangでも動作します)、他のコンパイラでコードの移植性を保つために、コンパイル時のマクロを使用する必要があります。このセクションはより高度なテクニックを含んでおり、何が起こっているのかをアセンブリレベルで理解していない場合は使用しないでください。また、最近のプロセッサーやコンパイラーはかなり賢いので、ここで説明する機能から顕著な利益を得るのは難しいかもしれないことに注意してください。

  • GCC ビルトイン、これはリストされています ここに . のようなコンストラクタは __builtin_expect を提供することで、コンパイラがより良い最適化を行うのを助けることができます。 分岐予測 の情報を提供することで、コンパイラがより良い最適化を行えるようにします。その他の構成要素である __builtin_prefetch などの構成は、アクセスされる前にデータをキャッシュに取り込みます。 キャッシュミス .
  • 関数属性の一覧です。 ここに . 特に hotcold 属性があります。前者はコンパイラにその関数が ホットスポット であることをコンパイラに示し、より積極的に関数を最適化し、テキストセクションの特別なサブセクションに配置し、より良いローカリティを実現します。

この回答が一部の開発者にとって有用であることを望みます。また、編集や提案を喜んで検討させていただきます。