【问题标题】:Does the C# compiler remove an if that encapsulates a debug.writelineC# 编译器是否删除了封装 debug.writeline 的 if
【发布时间】:2017-04-29 19:34:33
【问题描述】:

我有一段这样的代码:

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

如果我进行发布构建,编译器是否会对此进行优化?还是评估会保留并因此花费一些处理时间?

【问题讨论】:

  • 使用Debug.WriteLineIf 方法。
  • 注意:没有单一的 C# 编译器。有几个 C# 编译器,每个都发布了多个版本。 C# 规范几乎没有(如果有的话)强制性优化,因此您的答案只能针对特定编译器和特定版本。

标签: c# .net compiler-optimization


【解决方案1】:

是的,至少对于Debug 调用是这样。我在这里看不到 JIT 编译器是否也删除了对 if 的评估,但我猜它确实如此,因为该等式没有任何副作用。

但是,您最好通过调用 Debug.WriteLineIf 来保证它的安全,这不依赖于 JIT 编译器来删除评估。

为了完整性,编译器删除Debug.WriteLine的证明。


Release build 中的代码:

.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

如您所见,Release 模式没有调用 Debug.WriteLine,而 Debug 模式则调用。

【讨论】:

  • 据我所知没有。这是一个黑匣子。
  • 我不知道 Debug.WriteLineIf。我喜欢!谢谢!
  • 那么,如果您要求,JIT 删除的证据在哪里?
  • JIT 确实是一个黑匣子,它没有提供任何明确/记录的保证来保证它会删除 if 语句,但您当然可以凭经验验证它是否做或不做。在调试器下运行编译后的代码,看看反汇编。确保在调试器下运行时启用 JIT 优化;我不记得它在 VS 中是否默认关闭。
  • 发布代码仍然调用System.String::op_Inequality。我想说它仍然评估 if 语句,但它被视为有一个空的主体。
【解决方案2】:

来自MSDN's page on the Debug class:

如果您使用 Debug 类中的方法来打印调试信息并使用断言检查您的逻辑,您可以使您的代码更加健壮,而不会影响您交付产品的性能和代码大小。

...

ConditionalAttribute 属性应用于Debug 的方法。支持ConditionalAttribute 的编译器会忽略对这些方法的调用,除非“DEBUG”被定义为条件编译符号。

如您所见,编译器将忽略在非调试版本中对Debug 成员的任何调用。但是,它不会阻止程序检查您的 if 语句。如果您希望编译器也忽略 if 语句,您可以使用 preprocessor directive 将整个块括起来,如下所示:

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

【讨论】:

    【解决方案3】:

    语言规范要求 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# 编译器保持字符串不等式调用不变:

    我不确定规范是否允许对此进行优化,因为这可能是一种副作用。不确定编译器可以假设什么。

    我们出色的 JIT 也没有删除调用:

    (1) 是GetState(),(2) 是string.!=


    使用Debug.WriteLineIf,因为:

    17.4.2.1 条件方法 用 Conditional 属性修饰的方法是条件方法。 Conditional 属性通过测试条件编译符号来指示条件。根据在调用点是否定义了此符号,是否包含或省略对条件方法的调用。如果定义了符号,则包含调用;否则,调用(包括接收方的评估和调用的参数)将被省略。

    【讨论】:

    • 您能否参考规范中指出必须删除Debug.X 调用的位置?
    • @JeroenVannevel 我补充说。
    猜你喜欢
    • 2013-10-15
    • 1970-01-01
    • 2013-12-21
    • 1970-01-01
    • 1970-01-01
    • 2014-04-18
    • 2013-05-19
    • 2011-03-18
    • 1970-01-01
    相关资源
    最近更新 更多