C/C++ プログラムではデバッグ モードで最適化がオフになることが多いのはなぜですか?

StackOverflow https://stackoverflow.com/questions/69250

  •  09-06-2019
  •  | 
  •  

質問

ほとんどの C または C++ 環境には、「デバッグ」モードと「リリース」モードのコンパイルがあります。
2 つの違いを見ると、デバッグ モードではデバッグ シンボル (多くのコンパイラでは -g オプション) が追加されますが、ほとんどの最適化も無効になることがわかります。
「リリース」モードでは、通常、あらゆる種類の最適化がオンになっています。
なぜ違いがあるのでしょうか?

役に立ちましたか?

解決

最適化を行わないと、コードのフローは直線的になります。5 行目でシングルステップの場合は、6 行目に進みます。最適化をオンにすると、命令の並べ替え、ループの展開、その他あらゆる種類の最適化が可能になります。
例えば:


void foo() {
1:  int i;
2:  for(i = 0; i < 2; )
3:    i++;
4:  return;

この例では、最適化を行わずに、コードを 1 ステップずつ実行して、行 1、2、3、2、3、2、4 をヒットすることができます。

最適化をオンにすると、次のような実行パスが得られる場合があります。2、3、3、4、あるいは 4 つだけでも!(この関数は結局何もしません...)

結論から言えば、最適化を有効にしてコードをデバッグするのは非常に面倒な作業になる可能性があります。特に大規模な関数がある場合はそうです。

最適化をオンにするとコードが変更されることに注意してください。特定の環境 (安全性が重要なシステム) では、これは受け入れられず、デバッグされるコードは出荷されたコードでなければなりません。その場合は最適化をオンにしてデバッグする必要があります。

最適化されたコードと最適化されていないコードは「機能的に」同等である必要がありますが、特定の状況下では動作が変わります。
以下に単純な例を示します。

    int* ptr = 0xdeadbeef;  // some address to memory-mapped I/O device
    *ptr = 0;   // setup hardware device
    while(*ptr == 1) {    // loop until hardware device is done
       // do something
    }

最適化をオフにすると、これは簡単で、何が起こるかは大体わかります。ただし、最適化をオンにすると、次のようなことが起こる可能性があります。

  • コンパイラーは while ブロックを最適化する可能性があります (0 に初期化しますが、1 になることはありません)。
  • メモリにアクセスする代わりに、ポインタ アクセスがレジスタに移動される可能性があります -> I/O 更新なし
  • メモリアクセスがキャッシュされる可能性があります (必ずしもコンパイラの最適化に関連しているわけではありません)

これらすべてのケースで、動作は大幅に異なり、おそらく間違っています。

他のヒント

デバッグとリリースのもう 1 つの重要な違いは、ローカル変数の保存方法です。概念的には、ローカル変数は関数スタック フレームに割り当てられたストレージです。コンパイラによって生成されたシンボル ファイルは、スタック フレーム内の変数のオフセットをデバッガに伝えるため、デバッガはそれを表示できます。デバッガはこれを行うためにメモリの場所を調べます。

ただし、これは、ローカル変数が変更されるたびに、そのソース行に対して生成されたコードが値をスタック上の正しい場所に書き戻す必要があることを意味します。これはメモリのオーバーヘッドが発生するため、非常に非効率的です。

リリース ビルドでは、コンパイラが関数の一部のレジスタにローカル変数を割り当てる場合があります。場合によっては、スタック ストレージをまったく割り当てないこともあります (マシンのレジスタが多いほど、これが容易になります)。

ただし、デバッガは、コード内の特定のポイントでレジスタがローカル変数にどのようにマップされるかを知りません (この情報を含むシンボル形式は知りません)。そのため、デバッガはそれを正確に表示できません。どこに行けば探せるのか分かりません。

もう 1 つの最適化は、関数のインライン化です。最適化されたビルドでは、関数が十分に小さいため、コンパイラーは foo() への呼び出しを、使用されるすべての場所で foo の実際のコードに置き換える可能性があります。ただし、foo() にブレークポイントを設定しようとすると、デバッガは foo() の命令のアドレスを知りたがります。これに対する単純な答えはもうありません。foo() のコピーが何千も存在する可能性があります。 ) コードバイトがプログラム全体に分散されます。デバッグ ビルドでは、ブレークポイントを配置できる場所が確実に存在します。

コードの最適化は、セマンティクスを維持しながらコードの実行時のパフォーマンスを向上させる自動プロセスです。このプロセスでは、式または関数の評価を完了するためには不要ですが、デバッグ時に重要になる可能性のある中間結果を削除できます。同様に、最適化によって見かけの制御フローが変更され、ソース コードに表示される順序とはわずかに異なる順序で物事が発生する可能性があります。これは、不必要な計算や冗長な計算をスキップするために行われます。このコードの再調整により、ソース コードの行番号とオブジェクト コードのアドレス間のマッピングが混乱し、デバッガが作成時の制御フローに従うことが困難になる可能性があります。

非最適化モードでデバッグすると、オプティマイザーが内容を削除したり並べ替えたりすることなく、記述した内容をすべてそのまま表示できます。

プログラムが正しく動作していることに満足したら、最適化をオンにしてパフォーマンスを向上させることができます。最近のオプティマイザーはかなり信頼できるものになっていますが、最適化モードと非最適化モードの両方でプログラムが (パフォーマンスを考慮せず、機能の観点から) 同じように動作することを確認するには、高品質のテスト スイートを構築することをお勧めします。

デバッグ バージョンがデバッグされることが期待されています。ブレークポイントの設定、変数、スタック トレースを監視しながらのシングル ステップ実行、およびデバッガー (IDE またはその他) で行うその他すべての操作は、空ではなくコメントのないソース コードのすべての行が何らかのマシン コード命令と一致する場合に意味があります。

ほとんどの最適化では、マシン コードの順序が混乱します。ループ展開が良い例です。共通の部分式はループから取り出すことができます。最も単純なレベルであっても最適化をオンにすると、マシンコード レベルでは存在しない行にブレークポイントを設定しようとする可能性があります。場合によっては、ローカル変数が CPU レジスタに保持されているか、最適化されて存在しないため、ローカル変数を監視できないことがあります。

ソース レベルではなく命令レベルでデバッグしている場合、最適化されていない命令をソースにマップし直すのは非常に簡単です。また、コンパイラのオプティマイザにバグがある場合があります。

Microsoft の Windows 部門では、すべてのリリース バイナリがデバッグ シンボルと完全な最適化を使用して構築されています。シンボルは別の PDB ファイルに保存され、コードのパフォーマンスには影響しません。製品には同梱されていませんが、ほとんどの製品は次のサイトで入手できます。 Microsoft シンボル サーバー.

最適化に関するもう 1 つの問題は、インライン関数を常にシングルステップで実行するという意味でもあります。

GCC では、デバッグと最適化が同時に有効になっているため、何が起こるか分からない場合、コードが誤動作し、同じステートメントを複数回再実行していると考えるでしょう。それは私の同僚数人に起こりました。また、最適化を有効にした GCC によって提供されるデバッグ情報は、実際に提供される情報よりも品質が低くなる傾向があります。

ただし、Java などの仮想マシンによってホストされる言語では、最適化とデバッグが共存できます。デバッグ中であっても、ネイティブ コードへの JIT コンパイルは続行され、デバッグされたメソッドのコードのみが最適化されていないバージョンに透過的に変換されます。

使用するオプティマイザーにバグがあるか、コード自体にバグがあり、部分的に未定義のセマンティクスに依存している場合を除き、最適化によってコードの動作が変更されるべきではないことを強調したいと思います。後者は、マルチスレッド プログラミングやインライン アセンブリも使用される場合に一般的です。

デバッグ シンボルを含むコードは大きくなり、キャッシュ ミスが増える可能性があります。これはサーバー ソフトウェアの問題である可能性があります。

少なくとも Linux (Windows が異なる理由はありません) では、デバッグ情報はバイナリの別のセクションにパッケージ化されており、通常の実行中にはロードされません。これらは、デバッグに使用するために別のファイルに分割できます。また、一部のコンパイラ (Gcc を含む。Microsoft の C コンパイラも含むと思います) では、デバッグ情報と最適化の両方を同時に有効にすることができます。そうしないと、明らかにコードが遅くなります。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top