問題
シミュレーションタスク用の中規模プログラムがあり、最適化する必要があります。 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アーキテクチャに固有の命令の使用を有効にします。これは、異なるアーキテクチャで必ずしも使用できるとは限りません。別のCPUを搭載したシステムでプログラムを実行すると、プログラムがまったく機能しないか、大幅に遅くなる(mtune=native
も有効になるため)ので、使用する場合はこのことに注意してください。詳細情報 こちら 。-Ofast
は、あなたが述べたように、いくつかの非標準準拠最適化を有効にするため、同様に注意して使用する必要があります。詳細情報 こちら 。試す他のGCCフラグ
さまざまなフラグの詳細がリストされています here 。
-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
などの一部のextra flagsを選択的に追加します。これらは-Ofast
に含まれていないため、計算のパフォーマンスがさらに向上する可能性がありますが、実際にメリットがあり、計算を中断しないかどうかを確認する必要があります。-frename-registers
を使用します。このオプションは私にとって望ましくない結果を生んだことはなく、顕著なパフォーマンスの向上をもたらす傾向があります(つまり、ベンチマーク時に測定できます)。これは、プロセッサに非常に依存しているフラグのタイプです。また、-funroll-loops
は良い結果をもたらす場合があります(また、-frename-registers
を意味します)が、実際のコードに依存します。[〜#〜] pgo [〜#〜]
GCCにはProfile-Guided Optimisations機能があります。それについての正確なGCCドキュメントはあまりありませんが、それでもそれを実行するのは非常に簡単です。
-fprofile-generate
を使用してプログラムをコンパイルします。-fprofile-use
を使用してプログラムを再コンパイルします。アプリケーションがマルチスレッドの場合は、-fprofile-correction
フラグも追加します。GCCを使用したPGOは、驚くべき結果をもたらし、パフォーマンスを大幅に向上させます(最近取り組んでいたプロジェクトの1つで15〜20%の速度向上が見られました)。明らかに、ここでの問題は、アプリケーションの実行に関する十分に代表的なデータを持つことです。
GCCのパラレルモード
GCCはParallel Modeを備えています。これは、GCC 4.2コンパイラがリリースされた頃に最初にリリースされました。
基本的に、C++標準ライブラリの多くのアルゴリズムの並列実装を提供します。それらをグローバルに有効にするには、コンパイラに-fopenmp
フラグと-D_GLIBCXX_PARALLEL
フラグを追加するだけです。必要に応じて各アルゴリズムを選択的に有効にすることもできますが、これにはいくつかの小さなコード変更が必要になります。
この並列モードに関するすべての情報は、 here にあります。
これらのアルゴリズムを大規模なデータ構造で頻繁に使用し、多くのハードウェアスレッドコンテキストを使用できる場合、これらの並列実装によりパフォーマンスが大幅に向上します。これまでsort
の並列実装のみを使用していましたが、大まかなアイデアを出すために、アプリケーションの1つで14秒から4秒にソートする時間を短縮することができました(テスト環境:100のベクトルカスタムコンパレータ機能と8コアマシンを備えた数百万のオブジェクト)。
追加のトリック
前のポイントのセクションとは異なり、このパートではコードの若干の変更が必要を実行します。また、GCC固有であるため(一部はClangでも機能します)、コンパイル時マクロを使用して、コードを他のコンパイラーで移植可能にする必要があります。このセクションには、より高度なテクニックが含まれています。アセンブリレベルで何が起こっているのかを理解していない場合は使用しないでください。また、最近ではプロセッサーとコンパイラーは非常に賢いため、ここで説明する関数から顕著な利点を得るのは難しいかもしれません。
__builtin_expect
などのコンストラクトは、コンパイラに分岐予測情報を提供することで、最適化を改善するのに役立ちます。 __builtin_prefetch
などの他の構造体は、アクセスされる前にデータをキャッシュに取り込み、cache missesを減らすのに役立ちます。hot
およびcold
属性を調べる必要があります。前者は、関数がプログラムのhotspotであることをコンパイラに示し、関数をより積極的に最適化し、テキストセクションの特別なサブセクションに配置して、局所性を高めます。後者は関数をサイズ用に最適化し、テキストセクションの別の特別なサブセクションに配置します。この回答が一部の開発者にとって役立つことを願っており、編集や提案を検討できることを嬉しく思います。
比較的新しいハードウェア(Intel i5またはi7)
Intelコンパイラ および高性能ライブラリのコピーに投資してみませんか?最適化でGCCを大幅に上回ることができます。通常は10%から30%またはそれ以上であり、重い数値演算プログラムではさらに大きくなります。また、Intelは、コードに統合する余裕がある場合は、高性能の数値演算(並列)アプリケーション用の拡張機能とライブラリも多数提供しています。数か月分の実行時間を節約できれば、大きな見返りがあります。
プログラミングスキルの限界までソースを最適化するために、すでに最善を尽くしています。
私の経験では、プロファイラーを使用して通常行うマイクロ最適化とナノ最適化の種類は、マクロ最適化(コードの構造の合理化)と比較して、時間投資に対するリターンが低い傾向があります。しばしば見落とされがちな、メモリアクセスの最適化(たとえば、参照の局所性、順序通りの走査、間接化の最小化、キャッシュミスの使用など)。後者は通常、メモリの使用方法をよりよく反映する(トラバースする)メモリ構造の設計を伴います。場合によっては、コンテナの種類を切り替えて、そこからパフォーマンスを大幅に向上させるのと同じくらい簡単な場合もあります。多くの場合、プロファイラーを使用すると、命令ごとの最適化の詳細が失われ、メモリレイアウトの問題は表示されず、大きな画像を見忘れると通常見逃されます。それはあなたの時間を投資するためのはるかに良い方法であり、その見返りは莫大になる可能性があります(例えば、多くのO(logN)アルゴリズムはO(N)(たとえば、リンクリストまたはリンクツリーの使用は、連続したストレージ戦略と比較して、パフォーマンスの大きな問題の典型的な原因です)。
余裕があれば、 VTune を試してください。単純なサンプリング(私が知る限り、gprofが提供)よりも多くの情報を提供します。 Code Analyst を試してみてください。後者はまともな、無料のソフトウェアですが、Intel CPUでは正常に(またはまったく)動作しない可能性があります。
このようなツールが装備されているため、キャッシュの使用率(および基本的にはメモリレイアウト)などのさまざまな測定値を確認できます。これを使用すると、効率が大幅に向上します。
アルゴリズムと構造が最適であることを確認したら、i5とi7で複数のコアを確実に使用する必要があります。言い換えると、異なる並列プログラミングのアルゴリズム/パターンをいじって、スピードアップできるかどうかを確認してください。
本当に並列データ(同様の/同じ操作を実行する配列のような構造)がある場合は、OpenCLと SIMD命令 (セットアップが簡単)を試してください。
ええと、最後に試してみてください: [〜#〜] acovea [〜#〜] project:進化的アルゴリズムによるコンパイラ最適化の分析-説明から明らかなように、遺伝的アルゴリズムを試みますプロジェクトに最適なコンパイラオプションを選択するには(コンパイルを何回も行い、タイミングをチェックし、アルゴリズムにフィードバックを提供します:)-結果は印象的です! :)
現在選択されている回答に関するいくつかのメモ(これをコメントとして投稿するのに十分な評判ポイントがまだありません):
答えは言う:
-fassociative-math
、-freciprocal-math
、-fno-signed-zeros
、および-fno-trapping-math
。これらは-Ofast
に含まれていないため、計算のパフォーマンスがさらに向上します。
おそらく、回答が投稿されたときはこれが真実でしたが、 GCCドキュメンテーション は、これらのすべてが-funsafe-math-optimizations
によって有効化され、-ffast-math
によって有効化され、-Ofast
によって有効化されますこれは、コマンドgcc -c -Q -Ofast --help=optimizer
で確認できます。このコマンドは、-Ofast
によってどの最適化が有効になっているかを示し、これらすべてが有効になっていることを確認します。
答えも言います:
「-O」オプションによって有効にされないその他の最適化フラグ...
-frename-registers
繰り返しますが、上記のコマンドは、少なくとも私のGCC 5.4.0では、-frename-registers
がデフォルトで-Ofast
で有効になっていることを示しています。
詳細なしに答えることは困難です。
コードの中で最も時間がかかる部分を書き留めていただけますか? (通常、タイトループ)
CPUバウンドの場合、答えはIOバウンドの場合とは異なります。
繰り返しになりますが、詳細をお知らせください。
面倒な作業を引き起こす操作の種類を見て、最適化されたライブラリを探すことをお勧めします。一般的な問題(主に数学)のために、非常に高速でアセンブリ最適化されたSIMDベクトル化ライブラリが多数あります。車輪の再発明はしばしば魅力的ですが、既存のソリューションがニーズをカバーできる場合、通常は努力する価値はありません。