web-dev-qa-db-ja.com

C#コンパイラは、debug.writelineをカプセル化するifを削除しますか

私はこのようなコードを持っています:

if (state != "Ok")
{
     Debug.WriteLine($"Error occured: {state}, {moreInfo}");
}

リリースビルドを作成すると、コンパイラはこれを最適化しますか?または、評価が残っているため、処理時間がかかりますか?

38
fly3rbug

はい、少なくともDebugの呼び出しに対してはそうです。 JITコンパイラーがifの評価も削除したかどうかはここではわかりませんが、方程式には副作用がないため、削除したと思います。

ただし、Debug.WriteLineIfを呼び出して安全性を維持することをお勧めします。これは、評価の削除をJITコンパイラに依存しません。

完全を期すために、コンパイラがDebug.WriteLineを削除したことの証明。


リリースビルドのコード:

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       17 (0x11)
  .maxstack  8
  IL_0000:  call       string [mscorlib]System.Console::ReadLine()
  IL_0005:  ldstr      "Ok"
  IL_000a:  call       bool [mscorlib]System.String::op_Inequality(string,
                                                                   string)
  IL_000f:  pop
  IL_0010:  ret
} // end of method Program::Main

デバッグビルドのコード:

.method public hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       42 (0x2a)
  .maxstack  2
  .locals init ([0] string state,
           [1] bool V_1)
  IL_0000:  nop
  IL_0001:  call       string [mscorlib]System.Console::ReadLine()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  ldstr      "Ok"
  IL_000d:  call       bool [mscorlib]System.String::op_Inequality(string,
                                                                   string)
  IL_0012:  stloc.1
  IL_0013:  ldloc.1
  IL_0014:  brfalse.s  IL_0029
  IL_0016:  nop
  IL_0017:  ldstr      "Error occured: {0}"
  IL_001c:  ldloc.0
  IL_001d:  call       string [mscorlib]System.String::Format(string,
                                                              object)
  IL_0022:  call       void [System]System.Diagnostics.Debug::WriteLine(string)
  IL_0027:  nop
  IL_0028:  nop
  IL_0029:  ret
} // end of method Program::Main

ご覧のように、リリースモードにはDebug.WriteLineへの呼び出しがありません。デバッグモードとは異なります。

48
Patrick Hofman

From デバッグクラスのMSDNのページ:

Debugクラスのメソッドを使用してデバッグ情報を出力し、アサーションを使用してロジックを確認すると、出荷される製品のパフォーマンスとコードサイズに影響を与えることなく、コードをより堅牢にすることができます。

...

ConditionalAttribute属性は、Debugのメソッドに適用されます。 ConditionalAttributeをサポートするコンパイラは、 "DEBUG"が条件付きコンパイルシンボルとして定義されていない限り、これらのメソッドの呼び出しを無視します。

ご覧のように、コンパイラーは非デバッグビルドでのDebugメンバーへの呼び出しを省略します。ただし、プログラムがifステートメントをチェックするのを妨げることはありません。コンパイラーがifステートメントも無視するようにしたい場合は、 プリプロセッサーディレクティブ を使用して、次のようにブロック全体を囲むことができます。

#if DEBUG
if (state != "Ok")
{
    Debug.WriteLine($"Error occured: {state}, {moreInfo}");
}
#endif
15
Abion47

言語仕様では、C#コンパイラは、Debug呼び出しおよびの引数の評価を削除する必要があります。

.NET JITが洗練されたJITである場合、文字列メソッド呼び出しは副作用がなく、削除できると判断します。 .NET JITはあまり洗練されていないため、実際にはそのメソッドを呼び出す可能性があります。確認してみましょう。

プログラムをリリースモードでコンパイルし、逆コンパイルして、デバッガーが最適化を抑制せずに4.6.2でx64として実行します。

_    static void Main()
    {
        var state = GetState();
        if (state != "Ok")
        {
            Debug.WriteLine(state);
        }
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    static string GetState()
    {
        return "x";
    }
_

C#コンパイラは文字列不等式呼び出しをそのまま残しました:

enter image description here

enter image description here

仕様がこれを最適化することを許可するかどうかはわかりません。これは副作用のメソッドになる可能性があるためです。コンパイラがそれについて何を想定できるかわからない。

私たちの素晴らしいJITも呼び出しを削除しませんでした:

enter image description here

(1)はGetState()で、(2)は_string.!=_です。


_Debug.WriteLineIf_を使用する理由:

17.4.2.1条件付きメソッドConditional属性で修飾されたメソッドは、条件付きメソッドです。条件付き属性は、条件付きコンパイルシンボルをテストして条件を示します。条件付きメソッドの呼び出しは、このシンボルが呼び出しの時点で定義されているかどうかに応じて、含まれるか省略されます。シンボルが定義されている場合、呼び出しが含まれます。それ以外の場合、呼び出し(呼び出しのレシーバーとパラメーターの評価を含む)は省略されます。

4
usr