MS C#编译器和非优化代码

注意:我发现我发布的示例中有一些错误 – 编辑修复它

如果你不启用优化,官方的C#编译器会做一些有趣的事情。

例如,一个简单的if语句:

int x; // ... // if (x == 10) // do something 

如果优化,会变成以下内容:

 ldloc.0 ldc.i4.s 10 ceq bne.un.s do_not_do_something // do something do_not_do_something: 

但如果我们禁用优化,它会变成这样:

 ldloc.0 ldc.i4.s 10 ceq ldc.i4.0 ceq stloc.1 ldloc.1 brtrue.s do_not_do_something // do something do_not_do_something: 

我无法理解这一点。 为什么所有额外的代码,似乎在源中不存在? 在C#中,这相当于:

 int x, y; // ... // y = x == 10; if (y != 0) // do something 

有谁知道为什么这样做?

我不完全理解问题的重点。 听起来你问“为什么编译器会在优化开关关闭时生成未经优化的代码?” 有点回答自己。

但是,我会捅它。 我认为问题实际上就是“什么样的设计决策会导致编译器发出声明,存储和加载本地#1,哪些可以被优化掉?”

答案是因为未经优化的codegen被设计为清晰,明确,易于调试,并鼓励抖动生成不会积极收集垃圾的代码。 我们实现所有这些目标的方法之一是为堆栈中的大多数值生成局部变量,甚至是临时值。 我们来看一个更复杂的例子。 假设你有:

 Foo(Bar(123), 456) 

我们可以将其生成为:

 push 123 call Bar - this pops the 123 and pushes the result of Bar push 456 call Foo 

这是好的,有效的和小的,但它不符合我们的目标。 它清晰明确,但调试起来并不容易,因为垃圾收集器可能会变得激进。 如果Foo由于某种原因实际上没有对其第一个参数执行任何操作,则允许GC在Foo运行之前回收Bar的返回值。

在未经优化的构建中,我们将生成更像的东西

 push 123 call Bar - this pops the 123 and pushes the result of Bar store the top of the stack in a temporary location - this pops the stack, and we need it back, so push the value in the temporary location back onto the stack push 456 call Foo 

现在,抖动有一个很大的暗示,即“嘿抖动, 即使Foo不使用它,也要在当地保持活力一段时间

这里的一般规则是“在未优化的构建中使所有临时值中的局部变量”。 你去吧; 为了评估“if”语句,我们需要评估条件并将其转换为bool。 (当然条件不一定是bool类型;它可以是一个可以隐式转换为bool的类型,或者是一个实现操作符true / operator false对的类型。)未优化的代码生成器被告知“积极地转换所有临时值进入当地人“,这就是你得到的。

我想在这种情况下,我们可以压制那些在“如果”陈述条件下的临时工具,但这听起来像是为我工作而没有客户利益 。 因为只要你的arm确实有客户利益,我就有一堆工作,我不打算改变未经优化的代码生成器,它生成未经优化的代码,完全按照预期的那样。

我真的没有看到这个问题,所有优化的代码都是优化单个引用的本地(stloc ldloc组合)。

它在调试版本中出现的原因是,在使用它之前,您可以看到赋值给本地的值。

编辑:我现在看到其他额外的ceq

更新2:

我知道发生了什么。 由于布尔值表示为0和!0,调试版本进行第二次比较。 OTOH,优化器可能可以certificate代码的安全性。

未经优化的代码实际上就像:

 int x, _local; // _local is really bool _local = (x == 10) == 0; // ceq is ==, not <, not sure why you see that if (_local) // as in C, iow _local != 0 implied { ... } 

对于具体答案,您需要等待C#编译器团队中的某个人或与该组关系密切的人员详细解释此案例。

但是,这通常只是代码生成的工件,其中编写了常见例程来处理特定语句的许多不同情况,就像在您的情况下一样。

在某些情况下,这种泛化会产生function性但通常不是最佳的代码。 这就是为什么存在优化传递以对生成的代码进行各种优化以移除冗余代码,循环展开,窥视孔优化,代码共享等的原因。

在调试模式下编译时看到不太理想的代码的其他原因是支持调试器,例如,可能会在代码中插入NOP指令以便在调试器中运行时提供断点,但在发布版本中删除。