Why does the compiler do to optimize my code?
I have 2 functions:
public void x1() {
x++;
x++;
}
public void x2() {
x += 2;
}
public void x3() {
x = x + 2;
}
public void y3() {
x = x * x + x * x;
}
And that is what I can see with ILSpy after compiling in Release mode:
// test1.Something
public void x1()
{
this.x++;
this.x++;
}
// test1.Something
public void x2()
{
this.x += 2;
}
// test1.Something
public void x3()
{
this.x += 2;
}
// test1.Something
public void y3()
{
this.x = this.x * this.x + this.x * this.x;
}
x2 and x3 might be ok. But why is x1 not optimized to the same result? There is not reason to keep it a 2 step increment?
And why is y3 not x=2*(x*x)? Shouldn't that be faster than x*x+x*x?
That leads to the question? What kind of optimization does the C# compiler if not such simple things?
When I read articles about writing code you often hear, write it readable and the compiler will do the rest. But in this case the compiler does nearly nothing.
Adding one more example:
public void x1() {
int a = 1;
int b = 1;
int c = 1;
x = a + b + c;
}
and using ILSpy:
// test1.Something
public void x1()
{
int a = 1;
int b = 1;
int c = 1;
this.x = a + b + c;
}
Why is it not this.x = 3?
The compiler cannot perform this optimization without making an assumption that variable x is not accessed concurrently with your running method. Otherwise it risks changing the behavior of your method in a detectable way.
Consider a situation when the object referenced by this is accessed concurrently from two threads. Tread A repeatedly sets x to zero; thread B repeatedly calls x1().
If the compiler optimizes x1 to be an equivalent of x2, the two observable states for x after your experiment would be 0 and 2:
A finishes before B, you get 2B finishes before A, you get 0If A pre-empts B in the middle, you would still get a 2.
However, the original version of x1 allows for three outcomes: x can end up being 0, 1, or 2.
A finishes before B, you get 2B finishes before A, you get 0B gets pre-empted after the first increment, then A finishes, and then B runs to completion, you get 1.x1 and x2 are NOT the same:
if x were a public field and was accessed in a multi-threaded environment, it's entirely possible that a second thread mutates x between the two calls, which would not be possible with the code in x2.
For y2, if + and/or * were overloaded for the type of x then x*x + x*x could be different than 2*x*x.
The compiler will optimize things like (not an exhaustive list my any means):
Compiler optimization should NOT change the behavior of the program (although it does happen). So reordering/combining math operations are out of scope of optimization.
write it readable and the compiler will do the rest.
Well, the compiler may do some optimization, but there is still a LOT that can be done to improve performance at design-time. Yes readable code is definitely valuable, but the compiler's job is to generate working IL that corresponds with your source code, not to change your source code to be faster.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With