回答のほとんどは、さまざまなコンパイラや外部ライブラリなど、多くの書き換えや統合作業をもたらす可能性が高い代替ソリューションを示唆しています。OPの要求に応じて、コンパイラフラグをアクティブにするか、コードに最小限の変更を加えることで、質問の内容に固執し、GCCだけで何ができるかに焦点を当てます。これは「これをしなければならない」という答えではありませんが、私にとってうまく機能し、特定のコンテキストに関連する場合は試してみることができるGCCの微調整のコレクションです。
元の質問に関する警告
詳細に入る前に、質問に関するいくつかの警告があります。通常、一緒に来る人のために、質問を読んで、「OPはO3を超えて最適化されているので、彼と同じフラグを使用する必要があります!」と言います。
-march=native
特定のCPUアーキテクチャに固有の命令を使用できるようにします。これは、必ずしも別のアーキテクチャで使用できるとは限りません。別の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は、GCC4.2コンパイラがリリースされた頃に最初にリリースされたパラレルモードを備えています。
基本的に、C++標準ライブラリの多くのアルゴリズムの並列実装を提供します。それらをグローバルに有効にするには、コンパイラに-fopenmp
と-D_GLIBCXX_PARALLEL
フラグを追加するだけです。必要に応じて各アルゴリズムを選択的に有効にすることもできますが、これにはいくつかのマイナーなコード変更が必要になります。
この並列モードに関するすべての情報は、ここにあります。
大規模なデータ構造でこれらのアルゴリズムを頻繁に使用し、多くのハードウェアスレッドコンテキストを利用できる場合、これらの並列実装によりパフォーマンスが大幅に向上します。これまでの並列実装のみを使用しましたsort
が、大まかなアイデアを出すために、アプリケーションの1つでソートの時間を14秒から4秒に短縮することができました(テスト環境:カスタムコンパレータ機能を備えた1億個のオブジェクトのベクトルおよび8コアマシン)。
追加のトリック
前のポイントのセクションとは異なり、この部分ではコードに小さな変更を加える必要があります。これらはGCC固有でもあるため(一部はClangでも機能します)、コンパイル時マクロを使用して、コードを他のコンパイラーで移植可能に保つ必要があります。このセクションには、より高度な手法がいくつか含まれているため、アセンブリレベルで何が起こっているのかを理解していない場合は、使用しないでください。また、プロセッサとコンパイラは最近かなり賢いので、ここで説明する関数から目立った利点を得るのは難しいかもしれないことに注意してください。
- ここにリストされているGCCビルトイン。などの構造は、分岐予測情報
__builtin_expect
をコンパイラに提供することにより、コンパイラがより適切な最適化を行うのに役立ちます。データにアクセスする前にデータをキャッシュに取り込み、キャッシュミスを減らすのに役立つなどの他の構成。__builtin_prefetch
- ここにリストされている関数属性。
hot
特に、とcold
属性を調べる必要があります。前者は、関数がプログラムのホットスポットであることをコンパイラーに示し、関数をより積極的に最適化し、局所性を高めるためにテキストセクションの特別なサブセクションに配置します。後者は、関数のサイズを最適化し、テキストセクションの別の特別なサブセクションに配置します。
この回答が一部の開発者にとって役立つことを願っています。編集や提案を検討させていただきます。