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

时间:2010-09-07 13:37:38

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

注意:我在发布的示例中发现了一些错误 - 编辑修复

如果不启用优化,官方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

有谁知道为什么这样做?

3 个答案:

答案 0 :(得分:21)

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

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

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

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对的类型。)未经优化的代码生成器被告知“积极地转换所有临时值进入当地人“,这就是你得到的。

我想在这种情况下,我们可以在“如果”语句中的条件下抑制那些,但这听起来像为我工作而没有客户利益。因为只要你的手臂 有明显的客户利益,我就有一堆工作,我不打算改变未经优化的代码生成器,它生成未经优化的代码,完全按照预期的方式生成。

答案 1 :(得分:2)

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

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

编辑:我现在看到另一个额外的ceq

更新2:

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

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

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
{
  ...
}

答案 2 :(得分:1)

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

但是,这通常只是代码生成的工件,其中编写了常见例程来处理特定语句的许多不同情况,例如if

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

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