【问题标题】:How optimized is the C# compiler? [duplicate]C# 编译器的优化程度如何? [复制]
【发布时间】:2016-06-30 20:26:11
【问题描述】:

这段代码的IL代码(用https://dotnetfiddle.net生成):

public class Program
{
    public static void Main()
    {
        int i = 10;
        if (i < 4)
            Console.WriteLine("Hello World");
    }
}

包含 ldstr "Hello World"。

编译器不应该知道 Console.WriteLine 永远不会被执行吗?

这个的IL代码:

public class Program
{
    public static void Main()
    {
        if (10 < 4)
            Console.WriteLine("Hello World");
    }
}

不包含 ldstr 命令。

现在我很困惑.. .NET 编译器真的那么愚蠢吗? 两个示例的 C#/IL 代码完全相同:什么都没有。但是第一个例子的 IL 代码比另一个大。一个好的编译器不应该只调用构造函数而什么都不做吗?

编辑:

是的,我已经阅读了this,但我不是在谈论其他生成的本地人。

如果i 是属性或公共变量,则可以从另一个线程对其进行修改。但是i只存在于Main()中...

【问题讨论】:

  • 您是否尝试过开启和关闭优化?调试/发布模式。
  • 开启max优化和Release模式后,看看JIT生成了什么代码。那会很有趣。
  • 如果您在调试器中使用您的第一个代码 sn-p,则可以在 int i = 10;if (i &lt; 4) 之前立即停止。在这种情况下,调试器可以更改 i 的值,并且必须评估 if 语句的结果。
  • 永远不要说编译器是愚蠢的。这些野兽拥有超人的能力。如果您查看该网站上无数声称存在编译器错误的帖子,您就会明白我的意思。
  • @YvesDaoust 编译器有实际错误?你说得对,这种情况很少见,尽管确实会发生。编译器没有按照您期望的方式优化某些东西?我认为这种情况更常见(特别是因为编译器不允许执行一些“明显”的优化)。

标签: c# .net optimization compiler-optimization cil


【解决方案1】:

在我看来,编译器的行为符合预期。编译器通常只计算常量和常量表达式,因为它们的值在编译时是已知的。您示例中的变量“i”和表达式“i

【讨论】:

  • 我要补充一点,JIT 更有可能在为运行平台生成代码时确实优化了它
  • 是的,很可能是这样。
  • 这是错误的,常见的编译器(例如,对于 C++)执行常量传播,以便优化代码。
  • @svick,编译器确实传播常量是对的,但常量的值在编译时是已知的。该线程问题中的“i”是一个变量,它的值是在运行时评估的。这就是编译器在这种情况下不优化“if”子句的原因。
  • 常量10的值在编译时是已知的。常量传播意味着i 的值(技术上不是常量,但它的值实际上是常量)在编译时也是已知的。查看 Lucas 回答中的程序集:JIT 编译器确实优化了 if(任何优化的 C++ 编译器都会这样做)。
【解决方案2】:

这是你的 sn-p 的 x64 反汇编:

00007FF7C6083E0E  add         byte ptr [rax],al  
--- C:\Dev\Temp\Test\ConsoleApp\ConsoleApp\Program.cs --------------------------
            int i = 10;
00007FF7C6083E10  ret  
--- No source file -------------------------------------------------------------

这意味着,JIT 执行了死代码消除(ret = returnMain 函数立即退出)。

编译器只执行一些基本的优化,但大部分都留给了 JIT,以针对它运行的平台进行优化。

虽然我同意编译器在这种情况下当然可以执行这种优化,因为它是平台无关的。

【讨论】:

  • 好的,谢谢!但是为什么 int i = 10 会出现在你的 x64 disassembly-sn-p 中呢?
  • @michidk 这只是 VS 显示的源代码到程序集的映射,以帮助您找到汇编代码的方法。它向您显示这行代码大致映射到ret,因此本地甚至不存在。
【解决方案3】:

我也认为这不应该是调试器的责任。尽管您的样本中的一切都很明显,但在计算值时,每一行都会显示累积影响,这是一项巨大的工作量,将其视为计算可用的总棋步。 (国际象棋中可用的步数比宇宙中所有的星星还要多。)

【讨论】:

  • 我对这个答案一无所知。调试器与此有什么关系?这与国际象棋动作有什么关系? (听起来你在谈论组合爆炸,但我也看不出这与这里有什么关系。)
  • 要了解一行是否会被执行,或者一个变量是否过时,编译器或调试器必须检查函数中所有可用的逻辑。这就像计算所有可用的国际象棋移动一样。
猜你喜欢
  • 1970-01-01
  • 2014-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-09
  • 1970-01-01
  • 2020-11-29
相关资源
最近更新 更多