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


233

私はさまざまな情報源から聞いたことがあります(ほとんどが私の同僚からのものです)。 -O3 g ++のはどういうわけか「危険」であり、必要であることが証明されない限り一般に回避すべきであるとています。

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


38
未定義の動作に依存している場合にのみ危険です。そしてそれでも、何かを台無しにしたのが最適化レベルだったとしたら、私は驚くでしょう。
セスカーネギー

5
コンパイラーは、コードを正確にコンパイルしたかのように動作するプログラムを生成するように制限されています。-O3特にバグがあるとは思いませんか?特定の仮定に基づいて奇妙で素晴らしいことをする可能性があるため、未定義の動作が「悪化」する可能性がありますが、それはあなた自身の責任です。だから、一般的に、それは大丈夫だと思います。
BoBTFish

5
最適化レベルが高いほど、コンパイラのバグが発生しやすくなるのは事実です。私自身もいくつかのケースに遭遇しましたが、一般的にはまだかなりまれです。
ミスティシャル

21
-O2がオン-fstrict-aliasingになり、コードがそれでも存続する場合、他の最適化でも存続する可能性があります。と-fpredictive-commoning-O3いえ、それはにのみ存在し、それを有効にすると、同時実行性に関する誤った仮定によって引き起こされるコードのバグが有効になる可能性があります。コードの誤りが少ないほど、危険性の少ない最適化が行われます;-)
Steve Jessop '18

6
@PlasmaHH、「厳密な」はの適切な説明だとは思わない-Ofast、たとえば、NaNのIEEE準拠の処理をオフにする
Jonathan Wakely

回答:


223

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は標準に完全に準拠することを目的としていると結論づけます。


2
私はちょうど反対のようなものを使います。私は常に-Osまたは-O2を使用します(O2はより小さな実行可能ファイルを生成する場合があります)。プロファイリング後、実行時間のかかるコードの一部でO3を使用します。これだけで最大20%高速化できます。
CoffeDeveloper 2015

3
私はスピードのためにそれをします。ほとんどの場合、O3は物事を遅くします。正確な理由はわかりませんが、命令キャッシュを汚染していると思います。
CoffeDeveloper 2015

4
@DarioOO「コードの膨らみ」を訴えるのは人気のあることですが、ベンチマークに裏打ちされていることはほとんどありません。アーキテクチャに大きく依存しますが、公開されているベンチマーク(たとえばphoronix.com/…)を見るたびに、大多数のケースでO3が高速であることを示しています。コードの膨張が実際に問題であることを証明するために必要なプロファイリングと注意深い分析を見てきました。それは通常、極端な方法でテンプレートを採用する人々にのみ起こります。
Nir Friedman、

1
@NirFriedman:コンパイラーのインラインコストモデルにバグがある場合、または実行するターゲットとはまったく異なるターゲットを最適化する場合、問題が発生する傾向があります。興味深いことに、これはすべての最適化レベルに適用されます...
PlasmaHH 2015

1
@PlasmaHH:cmovを使用する問題は、一般的なケースでは修正が困難です。通常、データを並べ替えたばかりではないため、gccが分岐が予測可能かどうかを判断しようとしている場合、std::sort関数の呼び出しを探す静的分析が役に立たない可能性があります。stackoverflow.com/questions/109710/…のようなものを使用すると、ソートされた状態を利用するのに役立つか、ソースを記述できます。> = 128が表示されるまでスキャンしてから、集計を開始します。肥大化したコードについては、ええ、私はそれを報告するために移動するつもりです。:P
Peter Cordes

42

私のややチェックした経験で-O3は、プログラム全体に適用すると、ほとんどの場合(に比べて-O2)遅くなります。これは、積極的なループのアンロールとインライン化がオンになり、プログラムが命令キャッシュに適合しなくなるためです。大規模なプログラムの場合、これは-O2-Os

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


10
"ほとんどいつも"?それを "50-50"にすると、契約が成立します;-)。
No-Bugs Hare

12

-O3オプションは、より低いレベルの '-O2'および '-O1'のすべての最適化に加えて、関数のインライン化など、よりコストのかかる最適化をオンにします。'-O3'最適化レベルは、結果として生成される実行可能ファイルの速度を上げる可能性がありますが、そのサイズを増やすこともできます。これらの最適化が好ましくない状況下では、このオプションは実際にプログラムを遅くする可能性があります。


3
「見かけ上の最適化」によってプログラムが遅くなる可能性があることを理解していますが、GCC -O3によってプログラムが遅くなると主張する情報源はありますか?
Mooing Duck

1
@MooingDuck:ソースを引用することはできませんが、かなり小さなL1Iキャッシュ(約10kの命令)を備えた一部の古いAMDプロセッサでこのようなケースに遭遇したことを覚えています。グーグルにはもっと興味があると思いますが、特にループのアンロールなどのオプションはO3の一部ではなく、サイズが大幅に増加します。-Osは、実行可能ファイルを最小にしたい場合に使用します。-O2でもコードサイズが大きくなる可能性があります。さまざまな最適化レベルの結果を試すのに最適なツールは、gccエクスプローラーです。
PlasmaHH 2012

@PlasmaHH:実際、小さなキャッシュサイズは、コンパイラが台無しにする可能性があるものです。それは本当に良い例です。答えに入れてください。
Mooing Duck

1
@PlasmaHH Pentium IIIには16KBのコードキャッシュがありました。AMDのK6以上には、実際には32KBの命令キャッシュがありました。P4は約96KBの価値で始まりました。コアI7には実際には32KBのL1コードキャッシュがあります。命令デコーダは現在強力であるので、L3はほぼすべてのループにフォールバックできます。
doug65536 2013年

1
ループ内で呼び出される関数があると、パフォーマンスが大幅に向上し、共通の部分式を大幅に削除して、ループ前の関数から不要な再計算を行うことができます。
doug65536 2013年

8

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

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


3

最近、私は最適化を使用して問題を経験しました 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、先行するすべての書き込みを省略し。

結論として、最適化を使用して正確かつ注意深く行います。


38
ただし、ここでのポイントは、プログラムの動作が未定義であることだけです。オプティマイザは何も間違っていません。特にpciMemoryとして宣言する必要がありますvolatile
Konrad Rudolph

11
それは実際にはUBではありませんが、pciMemory他のすべての書き込みはおそらく効果がないため、コンパイラは最後の書き込み以外のすべてを省略する権利があります。多くの無駄で時間のかかる命令を削除できるため、すばらしいオプティマイザにとって。
Konrad Rudolph

4
私はこれを標準で見つけました(10年以上後)))- 揮発性宣言を使用して、メモリマップされた入出力ポートに対応するオブジェクト、または非同期割り込み関数によってアクセスされるオブジェクトを記述できます。そのように宣言されたオブジェクトに対するアクションは、式を評価するためのルールで許可されている場合を除き、実装によって「最適化」したり、並べ替えたりしてはなりません。
borisbn 2013年

2
@borisbn少し話題から外れていますが、デバイスが新しいコマンドを送信する前にコマンドを受け取ったことをどのようにして知るのですか?
user877329 2014

3
@ user877329デバイスの動作で見ましたが、すばらしいクエストでした
borisbn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.