Let's say I have class with field:
const double magicalConstant = 43;
This is somewhere in code:
double random = GetRandom();
double unicornAge = random * magicalConstant * 2.0;
Will compiler optimize my code so that it doesn't calculate magicalConstant * 2.0
every time it calcuates unicornAge
?
I know that I can define next const that takes this multiplication into account. But this looks much cleaner in my code. And it makes sense for compiler to optimize it.
When you declare a const in your program, int const x = 2; Compiler can optimize away this const by not providing storage for this variable; instead it can be added to the symbol table. So a subsequent read just needs indirection into the symbol table rather than instructions to fetch value from memory.
You can use pointers to constant data as function parameters to prevent the function from modifying a parameter passed through a pointer. For objects that are declared as const , you can only call constant member functions. The compiler ensures that the constant object is never modified.
(This question was the subject of my blog in October 2015; thanks for the interesting question!)
You have some good answers already that answer your factual question: No, the C# compiler does not generate the code to do a single multiplication by 86. It generates a multiplication by 43 and a multiplication by 2.
There are some subtleties here that no one has gone into though.
Multiplication is "left associative" in C#. That is,
x * y * z
must be computed as
(x * y) * z
And not
x * (y * z)
Now, is it the case that you ever get different answers for those two computations? If the answer is "no" then the operation is said to be an "associative operation" -- that is, it does not matter where we put the parentheses, and therefore can do optimizations to put the parentheses in the best place. (Note: I made an error in a previous edit of this answer where I said "commutative" when I meant "associative" -- a commutative operation is one where x * y is equal to y * x.)
In C#, string concatenation is an associative operation. If you say
myString + "hello" + "world" + myString
then you get the same result for
((myString + "hello") + "world") + myString
And
(myString + ("hello" + "world")) + myString
and therefore the C# compiler can do an optimization here; it can do a computation at compile time and generate the code as though you had written
(myString + "helloworld") + myString
which is in fact what the C# compiler does. (Fun fact: implementing that optimization was one of the first things I did when I joined the compiler team.)
Is a similar optimization possible for multiplication? Only if multiplication is associative. But it is not! There are a few ways in which it is not.
Let's look at a slightly different case. Suppose we have
x * 0.5 * 6.0
Can we just say that
(x * 0.5) * 6.0
is the same as
x * (0.5 * 6.0)
and generate a multiplication by 3.0? No. Suppose x is so small that x multiplied by 0.5 is rounded to zero. Then zero times 6.0 is still zero. So the first form can give zero, and the second form can give a non-zero value. Since the two operations give different results, the operation is not associative.
The C# compiler could have smarts added to it -- like I did for string concatenation -- to figure out in which cases multiplication is associative and do the optimization, but frankly it is simply not worth it. Saving on string concatenations is a huge win. String operations are expensive in time and memory. And it is very common for programs to contain very many string concatenations where constants and variables are mixed together. Floating point operations are very cheap in time and memory, it is hard to know which ones are associative, and it is rare to have long chains of multiplications in realistic programs. The time and energy it would take to design, implement and test that optimization would be better spent writing other features.
No, it doesn't in this case.
Look at this code:
const double magicalConstant = 43;
static void Main(string[] args)
{
double random = GetRandom();
double unicornAge = random * magicalConstant * 2.0;
Console.WriteLine(unicornAge);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static double GetRandom()
{
return new Random().NextDouble();
}
our dissassembly is:
double random = GetRandom();
00007FFDCD203C92 in al,dx
00007FFDCD203C93 sub al,ch
00007FFDCD203C95 mov r14,gs
00007FFDCD203C98 push rdx
double unicornAge = random * magicalConstant * 2.0;
00007FFDCD203C9A movups xmm1,xmmword ptr [7FFDCD203CC0h]
00007FFDCD203CA1 mulsd xmm1,xmm0
00007FFDCD203CA5 mulsd xmm1,mmword ptr [7FFDCD203CC8h]
Console.WriteLine(unicornAge);
00007FFDCD203CAD movapd xmm0,xmm1
00007FFDCD203CB1 call 00007FFE2BEDAFE0
00007FFDCD203CB6 nop
00007FFDCD203CB7 add rsp,28h
00007FFDCD203CBB ret
we have two mulsd
instructions here therefore we have two multiplications.
Now let's place some brackets:
double unicornAge = random * (magicalConstant * 2.0);
00007FFDCD213C9A movups xmm1,xmmword ptr [7FFDCD213CB8h]
00007FFDCD213CA1 mulsd xmm1,xmm0
as you can see, compiler optimized it.
In float-point worls (a*b)*c != a*(b*c)
, so it can't optimize it without manual help.
For example, integer code:
int random = GetRandom();
00007FFDCD203860 sub rsp,28h
00007FFDCD203864 call 00007FFDCD0EC8E8
int unicornAge = random * magicalConstant * 2;
00007FFDCD203869 imul eax,eax,2Bh
int unicornAge = random * magicalConstant * 2;
00007FFDCD20386C add eax,eax
with brackets:
int random = GetRandom();
00007FFDCD213BA0 sub rsp,28h
00007FFDCD213BA4 call 00007FFDCD0FC8E8
int unicornAge = random * (magicalConstant * 2);
00007FFDCD213BA9 imul eax,eax,56h
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