私が少し前に尋ねた質問の1つ は未定義の動作をしていたので、コンパイラの最適化が実際にプログラムを壊していた。
しかし、コードに未定義の動作がない場合、コンパイラの最適化を使用しない理由はありますか?デバッグの目的で、最適化されたコードが必要ない場合があることを理解しています(間違っている場合は修正してください)。それ以外に、本番コードでは、コンパイラの最適化を常に使用しないのはなぜですか?
また、たとえば、-O
または-O2
の代わりに-O3
を使用する理由はありますか?
未定義の動作はないが、明確に壊れた動作(確定的な通常のバグ、または競合状態のような不確定なもの)がある場合、デバッガーでコードをステップ実行できるように最適化をオフにすることは有益です。 ==
通常、このような状態になったら、次の組み合わせを実行します。
バグがもっとひどい場合は、問題を特定して確実にするために、valgrindとdrdを引き出し、必要に応じてnit-testsを追加します。問題が見つかったとき、解決策は期待どおりに機能します。
非常にまれなケースですが、デバッグコードは機能しますが、リリースコードは失敗します。これが発生した場合、ほとんどの場合、問題は私のコードにあります。リリースビルドでの積極的な最適化は、一時的なものの誤解されたライフタイムなどによって引き起こされたバグを明らかにする可能性があります... ...しかし、このような状況でも、デバッグビルドがあると問題を切り分けるのに役立ちます。
要するに、プロの開発者がデバッグ(最適化されていない)バイナリとリリース(最適化された)バイナリの両方を構築してテストする非常に良い理由がいくつかあります。 IMHOは、デバッグビルドとリリースビルドの両方で常に単体テストに合格することで、デバッグ時間を大幅に節約できます。
コンパイラの最適化には2つの欠点があります。
-O3によって実行される最適化の一部は、実行可能ファイルが大きくなる可能性があります。これは、一部の製品コードでは望ましくない場合があります。
最適化を使用しないもう1つの理由は、使用しているコンパイラに、最適化を実行しているときにのみ存在するバグが含まれている可能性があることです。最適化せずにコンパイルすると、これらのバグを回避できます。コンパイラにバグが含まれている場合は、それらのバグを報告/修正するか、より優れたコンパイラに変更するか、これらのバグを完全に回避するコードを作成することをお勧めします。
リリースされた製品コードでデバッグを実行できるようにしたい場合は、コードを最適化しないこともお勧めします。
ケース2では、ポインタの種類を意図的に変更するOSコードを想像してみてください。オプティマイザは、間違ったタイプのオブジェクトを参照できないと想定し、レジスタ内のメモリ値の変更をエイリアスして「間違った」コードを生成する可能性があります。1 回答。
ケース3は興味深い懸念事項です。オプティマイザーはコードを小さくすることもあれば、大きくすることもあります。ほとんどのプログラムはCPUに制限されているわけではなく、CPUに依存しているプログラムであっても、実際に計算量が多いのはコードの10%以下だけです。オプティマイザーにマイナス面がある場合、それはプログラムの10%未満の勝利にすぎません。
生成されたコードが大きい場合、キャッシュフレンドリーではなくなります。これは、O(n)を持つ行列代数ライブラリにとって価値があるかもしれません。3)小さな小さなループのアルゴリズム。しかし、より一般的な時間計算量のあるものの場合、キャッシュをオーバーフローさせると、実際にはプログラムが遅くなる可能性があります。オプティマイザーは通常、これらすべてに合わせて調整できますが、プログラムがWebアプリケーションの場合、たとえば、コンパイラーが多目的なことを実行し、開発者がを開かないようにすると、開発者にとってより使いやすくなります。ファンシートリック パンドラの箱。
1.このようなプログラムは通常、標準に準拠していないため、オプティマイザーは技術的に「正しい」ですが、それでも開発者が意図したことを実行していません。
その理由は、1つのアプリケーションを開発し(デバッグビルド)、顧客がまったく異なるアプリケーションを実行する(リリースビルド)ためです。テストリソースが少ない場合や、使用するコンパイラがあまり一般的でない場合は、リリースビルドの最適化を無効にします。
MSは、MSVCx86コンパイラの最適化バグに対する多数の修正プログラムを公開しています。幸いなことに、私は実生活でこれに遭遇したことはありません。しかし、これは他のコンパイラには当てはまりませんでした。 MS Embedded Visual C++のSH4コンパイラは非常にバグがありました。
私が見た2つの大きな理由は、浮動小数点演算と過度に積極的なインライン化から生じます。前者は、浮動小数点演算がC++標準で非常に不十分に定義されているという事実が原因です。多くのプロセッサは、80ビットの精度を使用して計算を実行します。たとえば、値がメインメモリに戻されると、64ビットにしか低下しません。あるバージョンのルーチンがその値をメモリに頻繁にフラッシュし、別のバージョンが最後に1回だけ値を取得する場合、計算の結果はわずかに異なる可能性があります。そのルーチンの最適化を微調整するだけで、コードをリファクタリングして違いに対してより堅牢にするよりも良い方法かもしれません。
インライン化は、その性質上、一般にオブジェクトファイルが大きくなるため、問題が発生する可能性があります。おそらく、この増加は、コードサイズが実際的な理由で受け入れられないことです。たとえば、メモリが限られているデバイスに収まる必要があります。または、コードサイズが大きくなると、コードが遅くなる可能性があります。ルーチンが十分に大きくなり、キャッシュに収まらなくなった場合、結果として生じるキャッシュミスは、最初に提供されたインライン化の利点をすぐに上回る可能性があります。
マルチスレッド環境で作業しているときに、デバッグをオフにして、新たに発見された競合状態などのためにすぐに新しいバグの大群に遭遇する人々のことをよく耳にします。ただし、オプティマイザーはここで根本的なバグのあるコードを明らかにしたので、それに応じてオフにすることはおそらくお勧めできません。
シンプル。コンパイラ最適化のバグ。
ちょうど私に起こった。 swig によって生成されたインターフェースJavaは正しいですが、gccの-O2では機能しません。
最適化フラグを使用すると時々が危険である理由の例があり、テストではそのようなエラーに気付くためにほとんどのコードをカバーする必要があります。
clangを使用する(gccでは最適化フラグがなくても、いくつかの最適化が行われ、出力が破損するため):
ファイル:a.cpp
#include <stdio.h>
int puts(const char *str) {
fputs("Hello, world!\n", stdout);
return 1;
}
int main() {
printf("Goodbye!\n");
return 0;
}
-Oxフラグなし:
> clang --output withoutOptimization a.cpp; ./withoutOptimization
> さようなら!
-Oxフラグ付き:
> clang --output withO1 -O1 a.cpp; ./withO1
> Hello、world!