1. ホーム
  2. c#

[解決済み】デバッグビルドとリリースビルドの性能差について

2022-04-15 09:44:56

質問

私は、普段から、わざわざ デバッグ リリース のコンフィギュレーションを使用しています。 デバッグ という構成で、実際にお客様のところにデプロイされる場合もあります。

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

そこで、質問は2つあります。

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

  2. の下で問題なく実行できるコードのタイプはありますか? デバッグ の下では失敗する可能性があります。 リリース の下でテストされ、問題なく動作しているコードを、本当に デバッグ の設定は、Releaseの設定でも問題なく動作します。

解決方法は?

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

  • メソッドのインライン化。 メソッド呼び出しは、メソッドのコードを注入することで置き換えられる。 これは大きなもので、プロパティアクセサを本質的に自由にするものです。

  • CPUレジスタの割り当て。 ローカル変数とメソッドの引数は、スタックフレームに戻されることなく(または頻度が少なくても)CPUレジスタに格納されたままにすることができます。 これは大きな問題で、最適化されたコードのデバッグを非常に難しくしていることで注目されています。 また ボラティリティ キーワードに意味を持たせています。

  • 配列のインデックスチェックの省略。 配列(すべての.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のジッターでは、扱うレジスタが少ないので、これは大きな問題です。 正しいレジスタを選択させることは、性能にとって非常に重要です。

これらは非常に重要な最適化であり、これにより 偉大な 例えば、アプリのデバッグビルドをプロファイリングして、リリースビルドと比較したときに、大きな違いが生まれます。 ただし、これはクリティカルパス上にあるコードに限った話です。 実際に プログラムの性能に影響を与えます。 JITオプティマイザは、何が重要かを前もって知るほど賢くないので、すべてのコードに「quot; turn it to eleven"」ダイヤルを適用するしかないのです。

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

JITオプティマイザはかなり信頼性の高いコードで、そのほとんどは何百万回もテストされているからです。 リリースビルド版で問題が発生することは極めて稀です。 しかし、実際に起こることもあります。 x64とx86の両方のジッターで、構造体に関する問題が発生しました。 x86 ジッターは浮動小数点数の整合性に問題があり、浮動小数点数計算の中間値を FPU レジスタに 80 ビット精度で保持すると、メモリにフラッシュする際に切り捨てられる代わりに、微妙に異なる結果を生成してしまうのです。