web-dev-qa-db-ja.com

最適化レベル-O3はg ++で危険ですか?

私はさまざまなソースから(主に私の同僚から)聞いていますが、g ++で-O3の最適化レベルでコンパイルすることは何らかの形で「危険」であり、必要であることが証明されない限り一般的に避けるべきです.

これは本当ですか、もしそうなら、なぜですか? -O2に固執する必要がありますか?

208
Dunnie

Gccの初期(2.8など)およびegcsの時代には、redhat 2.96 -O3は非常にバグが多かった。しかし、これは10年以上前であり、-O3は他のレベルの最適化(バグの多い)とそれほど変わりません。

しかし、言語のルール、特にコーナーケースに厳密に依存しているため、人々が未定義の動作に依存しているケースを明らかにする傾向があります。

個人的な注意として、私は長年にわたって金融業界で-O3を使用して運用ソフトウェアを実行しており、-O2を使用した場合には存在しなかったバグにまだ遭遇していません。

一般的な需要により、ここに追加:

-O3と、特に-funroll-loops(-O3によって有効にされない)のような追加のフラグは、より多くのマシンコードが生成される場合があります。特定の状況(例:L1命令キャッシュが非常に小さいCPU上)では、これにより、すべてのコードが原因でスローダウンが発生する可能性があります。一部の内部ループがL1Iに適合しなくなりました。一般的に、gccはそれほど多くのコードを生成しないようにかなり努力しますが、通常は一般的なケースを最適化するため、これが発生する可能性があります。これを特に起こしやすいオプション(ループの展開など)は、通常-O3には含まれておらず、マンページで適宜マークされています。そのため、高速コードを生成するために-O3を使用し、適切な場合(プロファイラーがL1Iミスを示す場合など)にのみ-O2または-Os(コードサイズの最適化を試みる)にフォールバックすることをお勧めします。

最適化を極端にしたい場合は、特定の最適化に関連するコストを--paramでgccで調整できます。さらに、gccにはこれらの関数の最適化設定を制御する関数に属性を設定できる機能があるため、1つの関数で-O3に問題がある場合(またはその関数だけに特別なフラグを試したい場合)、ファイル全体またはプロジェクト全体をO2でコンパイルする必要はありません。

otoh -Ofastの使用時には注意が必要なようです。

-Ofastは、すべての-O3最適化を有効にします。また、すべての標準準拠プログラムに有効ではない最適化を有効にします。

-O3は完全に標準に準拠することを意図していると結論付けられます。

200
PlasmaHH

これは、ニールの答えですでに述べられていますが、明白または十分に十分ではありません:

私のやや市松模様の経験では、-O3をプログラム全体に適用するとほぼ常に遅くなります(-O2に比べて)。 。大規模なプログラムの場合、これは-O2に対して-Osにも当てはまります!

-O3の使用目的は、プログラムをプロファイリングした後、これらの積極的なスペースと速度のトレードオフから実際に恩恵を受ける重要な内部ループを含む少数のファイルに手動で適用することです。非常に最近のGCCでは、Ithink光沢のある新しいリンク時プロファイルガイド最適化モードは、-O3最適化をホット関数に選択的に適用できます-これを効果的に自動化しますプロセス。

36
zwol

-O3オプションは、低レベルの「-O2」および「-O1」のすべての最適化に加えて、関数のインライン化など、より高価な最適化を有効にします。 「-O3」最適化レベルは、結果の実行可能ファイルの速度を上げる可能性がありますが、サイズも大きくなる可能性があります。これらの最適化が好ましくない状況では、このオプションは実際にプログラムを遅くする可能性があります。

8
neel

はい、O3はバグが多いです。私はコンパイラ開発者であり、独自のソフトウェアをビルドするときにO3がバグのあるSIMDアセンブリ命令を生成することによって引き起こされる、明確で明白なgccバグを特定しました。私が見たところから、ほとんどの本番ソフトウェアにはO2が同梱されています。つまり、O3はテストやバグ修正に関してあまり注目されていません。

このように考えてください。O3はO2の上にさらに変換を追加し、O1の上にさらに変換を追加します。統計的に言えば、より多くの変換はより多くのバグを意味します。これはどのコンパイラにも当てはまります。

6
David Yeager

最近、g++で最適化を使用する際に問題が発生しました。この問題は、レジスタ(コマンドおよびデータ用)がメモリアドレスによって表されるPCIカードに関連していました。私のドライバーは物理アドレスをアプリケーション内のポインターにマップし、呼び出されたプロセスにそれを渡しました。

unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
    pciMemory[ 0 ] = someCommand[ i ];

カードが期待どおりに機能しませんでした。アセンブリを見たとき、コンパイラがsomeCommand[ the last ]pciMemoryに書き込むだけで、先行する書き込みをすべて省略していることを理解しました。

結論として、最適化により正確で注意を払ってください。

4
borisbn