C#编译器会对此代码进行优化吗?

20 浏览
0 Comments

C#编译器会对此代码进行优化吗?

我经常遇到这种情况。乍一看,我认为:“这是糟糕的编码;我执行了两次方法,结果肯定是一样的。”但是在思考之后,我不得不怀疑编译器是否和我一样聪明,能够得出相同的结论。\n

var newList = oldList.Select(x => new Thing {
    FullName = String.Format("{0} {1}", x.FirstName, x.LastName),
    OtherThingId = x.GetOtherThing() != null : x.GetOtherThing().Id : 0 // 可能会调用x.GetOtherThing()两次吗?
});

\n编译器的行为是否取决于GetOtherThing方法的内容?假设它看起来像这样(与我现在的真实代码有些相似):\n

public OtherThing GetOtherThing() {
    if (this.Category == null) return null;
    return this.Category.OtherThings.FirstOrDefault(t => t.Text == this.Text);
}

\n除非对这些对象所来自的存储进行非常糟糕的异步更改处理,否则这将无疑在连续运行两次时返回相同的结果。但是,如果它看起来像这样(为了论证而无意义的示例):\n

public OtherThing GetOtherThing() {
    return new OtherThing {
        Id = new Random().Next(100)
    };
}

\n连续运行两次将导致创建两个不同的对象,很可能具有不同的Id。编译器在这些情况下会怎么做?执行我在第一个示例中展示的操作是否真的低效?\n

自己做一些工作

\n我运行了非常类似于第一个代码示例的代码,并在GetOtherThing实例方法中设置了一个断点。断点只触发了一次。所以,看起来结果确实被缓存了。在第二种情况下,方法每次可能返回不同的结果会发生什么?编译器会错误地进行优化吗?对我找到的结果有什么警告吗?\n编辑\n这个结论是无效的。请参阅 @usr 回答下的评论。

0
0 Comments

C#编译器能否对代码进行优化的问题,其原因是因为编译器只有在无法区分不同的情况下才能应用优化。在“random”示例中,你可以明显看出不同之处。它无法通过这种方式进行“优化”,因为这将违反C#规范。实际上,规范并没有详细讨论优化。它只说你应该观察程序的行为。在这种情况下,规范指定应该绘制两个随机数。

在第一个示例中,可能可以应用这种优化。但实际上从不会发生。以下是一些使之变得困难的因素:

- 查询操作的数据可能会被你的虚函数调用或Lambda表达式(`t => t.Text == this.Text`)更改。非常隐蔽。

- 它可能被另一个线程更改。我不确定.NET内存模型对此的规定。

- 它可能被反射更改。

- 必须能够证明计算总是返回相同的值。你如何证明呢?你需要分析所有可能运行的代码。包括虚函数调用和数据相关的控制流程。

所有这些都必须适用于非内联方法和跨程序集。

C#编译器无法做到这一点,因为它无法查看mscorlib。补丁版本可能随时更改mscorlib。

JIT是一个较差的JIT(遗憾的是),它优化的是编译速度(遗憾的是)。它不会这样做。如果你不确定当前JIT是否会进行一些高级优化,那么可以肯定它不会进行。

这听起来是一个非常好的答案,那么你如何解释我的断点只触发了一次呢?看起来的确进行了优化。

也许你进入了`: 0`分支,或者你犯了其他错误。在所有3个位置(对于一些有用的Write函数,例如`Write() ? Write() : Write()`),加入`Console.WriteLine`调用。你总是会看到两次调用。在发布模式下,由于优化(与此处讨论的所谓优化无关),有时断点不会被触发。

哦,嗯,你说得对,这在这种情况下是三元语句的“false”分支。可惜,我的代码结构在过去半个小时内已经改变了,所以现在不方便进一步测试。我会相信你的话,并在下次遇到时确保进行更多测试。谢谢!

0
0 Comments

C#编译器将C#代码转换为中间语言(IL),而IL编译器将IL转换为机器码。微软的C#编译器并不会对代码进行此类优化,它会将方法调用生成为方法调用的形式。然而,IL编译器允许进行所描述的优化,前提是这种优化是无法被检测到的。具体来说,如果在代码中存在类似于"y = M() != 0 ? M() : N()"的表达式,IL编译器可以将其优化为"y = 1 != 0 ? 1 : N()"或者"y = 1"。类似地,如果存在类似于"static int m; static int M() { return m; }"的代码片段,IL编译器可以将其优化为"y = m != 0 ? m : N()"或者"int q = m; y = q != 0 ? q : N()"。然而,如果第二个调用具有副作用,IL编译器无法消除第二个调用。此外,当进行调试时,几乎所有的优化都会被关闭,以便更容易进行调试。因此,原帖中提到的在GetOtherThing实例方法中设置断点只被触发一次的情况是非常不可能的,最可能的解释是原帖作者搞错了。

0