Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

.net - MS C# compiler and non-optimized code

Note: I noticed some errors in my posted example - editing to fix it

The official C# compiler does some interesting things if you don't enable optimization.

For example, a simple if statement:

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

becomes something like the following if optimized:

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

but if we disable optimization, it becomes something like this:

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:

I can't quite get my head around this. Why all that extra code, which is seemingly not present in the source? In C#, this would be the equivalent of:

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

Does anyone know why it does this?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I don't fully understand the point of the question. It sounds like you're asking "why does the compiler produce unoptimized code when the optimization switch is off?" which kinda answers itself.

However, I'll take a stab at it. I think the question is actually something like "what design decision causes the compiler to emit a declaration, store and load of local #1, which can be optimized away?"

The answer is because the unoptimized codegen is designed to be clear, unambiguous, easy to debug, and to encourage the jitter to generate code that does not aggressively collect garbage. One of the ways we achieve all those goals is to generate locals for most values that go on the stack, even temporary values. Let's take a look at a more complicated example. Suppose you have:

Foo(Bar(123), 456)

We could generate this as:

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

That is nice and efficient and small, but it does not meet our goals. It is clear and unambiguous, but it is not easy to debug because the garbage collector could get aggressive. If Foo for some reason does not actually do anything with its first argument then the GC is allowed to reclaim the return value of Bar before Foo runs.

In the unoptimized build we would generate something more like

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

Now the jitter has a big hint that says "hey jitter, keep that alive in the local for a while even if Foo doesn't use it"

The general rule here is "make local variables out of all temporary values in the unoptimized build". And so there you go; in order to evaluate the "if" statement we need to evaluate a condition and convert it to bool. (Of course the condition need not be of type bool; it could be of a type implicitly convertible to bool, or a type that implements an operator true/operator false pair.) The unoptimized code generator has been told "aggressively turn all temporary values into locals", and so that's what you get.

I suppose in this case we could suppress that on temporaries that are conditions in "if" statements, but that sounds like making work for me that has no customer benefit. Since I have a stack of work as long as your arm that does have tangible customer benefit, I'm not going to change the unoptimized code generator, which generates unoptimized code, exactly as it is supposed to.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

2.1m questions

2.1m answers

60 comments

57.0k users

...