デバッグビルドとリリースビルドのパフォーマンスの違い 質問する

デバッグビルドとリリースビルドのパフォーマンスの違い 質問する

正直に言うと、私は通常、自分のプログラムでデバッグ構成リリース構成を切り替えることはせず、プログラムが実際に顧客の場所に展開されているときでも、通常はデバッグ構成を選択することを選択しています。

私の知る限り、手動で変更しない場合、これらの構成の唯一の違いは、デバッグでは定数DEBUGが定義され、リリースではコードの最適化がチェックされていることです。

私の質問は実際には2つあります:

  1. これら 2 つの構成には、パフォーマンスに大きな違いがありますか。ここでパフォーマンスに大きな違いをもたらす特定の種類のコードはありますか、それとも実際にはそれほど重要ではありませんか?

  2. デバッグ構成では正常に実行されるが、リリース構成では失敗する可能性があるコードの種類はありますか。または、デバッグ構成でテストされ正常に動作するコードは、リリース構成でも正常に動作すると確信できますか。

ベストアンサー1

C# コンパイラ自体は、リリース ビルドで出力される IL を大幅に変更しません。注目すべきは、中括弧にブレークポイントを設定できる NOP オペコードが出力されなくなったことです。大きな変更点は、JIT コンパイラに組み込まれているオプティマイザーです。次の最適化が行われることがわかっています。

  • メソッドのインライン化。メソッド呼び出しは、メソッドのコードの挿入によって置き換えられます。これは大きな利点であり、プロパティ アクセサーが実質的に無料になります。

  • CPU レジスタの割り当て。ローカル変数とメソッド引数は、スタック フレームにまったく (またはあまり頻繁に) 格納されることなく、CPU レジスタに格納されたままになることがあります。これは大きな問題で、最適化されたコードのデバッグが非常に困難になることで有名です。また、volatileキーワードに意味を与えます。

  • 配列のインデックス チェックの排除。配列を操作する際の重要な最適化 (すべての .NET コレクション クラスは内部で配列を使用します)。ループが配列を範囲外にインデックス付けしないことを JIT コンパイラが確認できる場合、インデックス チェックを排除します。これは大きなことです。

  • ループの展開。本体が小さいループは、本体内でコードを最大 4 回繰り返し、ループ回数を減らすことで改善されます。分岐コストが削減され、プロセッサのスーパースカラー実行オプションが向上します。

  • デッドコードの除去。if (false) { / ... / }のようなステートメントは完全に除去されます。これは、定数の折りたたみとインライン化によって発生する可能性があります。その他のケースとしては、JIT コンパイラがコードに副作用がないと判断できる場合などがあります。この最適化により、コードのプロファイリングが非常に難しくなります。

  • コード ホイスト。ループの影響を受けないループ内のコードは、ループ外に移動できます。C コンパイラのオプティマイザーは、ホイストの機会を見つけるために多くの時間を費やします。ただし、必要なデータ フロー分析のためにコストのかかる最適化であり、ジッターには時間をかけられないため、明らかなケースのみをホイストします。.NET プログラマーは、より優れたソース コードを記述し、自分自身でホイストする必要があります。

  • 一般的なサブ式の削除。x = y + 4; z = y + 4; は z = x; になります。これは、ヘルパー変数を導入せずに読みやすさを重視して記述された dest[ix+1] = src[ix+1]; などのステートメントでよく使用されます。読みやすさを犠牲にする必要はありません。

  • 定数の畳み込み。 x = 1 + 2; は x = 3; になります。 この単純な例はコンパイラによって早期に検出されますが、他の最適化によってこれが可能になったときに JIT 時に発生します。

  • コピーの伝播。x = a; y = x; は y = a; になります。これにより、レジスタ アロケータが適切な決定を下せるようになります。x86 ジッタでは、処理するレジスタが少ないため、これは大きな問題です。適切なレジスタを選択させることは、パフォーマンスにとって重要です。

これらは非常に重要な最適化であり、たとえば、アプリのデバッグ ビルドをプロファイルしてリリース ビルドと比較すると、大きな違いが生まれます。ただし、これが本当に重要になるのは、コードがクリティカル パス上にある場合、つまり、実際にプログラムのパフォーマンスに影響を与えるのは、記述するコードの 5 ~ 10% です。JIT オプティマイザーは、何がクリティカルかを事前に把握できるほど賢くなく、すべてのコードに対して「11 に上げる」ダイヤルを適用することしかできません。

プログラムの実行時間に対するこれらの最適化の効果的な結果は、他の場所で実行されるコードによって影響を受けることがよくあります。ファイルの読み取り、データベースクエリの実行など。JIT オプティマイザーが行う作業は完全に見えなくなります。しかし、それは問題ではありません :)

JIT オプティマイザーは、何百万回もテストされているため、かなり信頼性の高いコードです。プログラムのリリース ビルド バージョンで問題が発生することは極めてまれです。ただし、発生することもあります。x64 ジッターと x86 ジッターの両方で、構造体に問題がありました。x86 ジッターは浮動小数点の一貫性に問題があり、浮動小数点計算の中間値がメモリにフラッシュされるときに切り捨てられるのではなく、80 ビット精度で FPU レジスタに保持されると、微妙に異なる結果が生成されます。

おすすめ記事