It is hard to answer this question adequately for an arbitrary compiler. What could be done with this code depends not only on a compiler, but also on a target architecture. I'll try to explain what a production compiler with good capabilities could do to this code.
From a processing-time point of view it would make sense to only compute the product of variable1 and variable2 once as they don't change in the loop.
You are right. And as Mr. Cat has pointed out, this is called common subexpression elimination. Thus, compiler may generate the code computes the expression only once (or even computes it in compile-time if values for two operands are known to be constant at a time).
A decent compiler may also perform subexpression elimination on functions if it can determine that functions have no side-effects. GCC, for example, can analyze a function if its body is available, but there are also pure
and const
attributes that can be used to specifically mark functions that should be subject to this optimization (see Function Attributes).
Given that there is no side effect and compiler is able to determine it (in your example, there is nothing standing in a way), two of the snippets are equivalent in this regard (I've checked with clang :-)).
This requires extra memory however, and I'm not sure how strongly an optimiser factors this overhead in.
In fact, this doesn't require any extra memory. The multiplication is done in processor registers and a result is stored in a register as well. It is a matter of eliminating a lot of code and using a single register to store a result, which is always great (and most certainly makes life easier when it comes to register allocation, especially in a loop). So if this optimization can be done then it will be done at no extra cost.
The first expression is the easiest to read..
Both GCC and Clang will perform this optimization. I am not sure about other compilers, though, so you will have to check for yourself. But it is hard to imagine any good compiler that is not doing subexpression elimination.
Does any of this change if I declare my variables as constants?
It may. This is called a constant expression — an expression that contains only constants. A constant expression can be evaluated during compilation rather than at run time. So, for example, if you multiple A, B and C where both A and B are constants, compiler will pre-compute A*B
expression only multiple C against that pre-computed value. Compilers may also do this even with non-constant values if they can determine its value at compile-time and be sure that it is not changed. For example:
$ cat test.c
inline int foo(int a, int b)
{
return a * b;
}
int main() {
int a;
int b;
a = 1;
b = 2;
return foo(a, b);
}
$ clang -Wall -pedantic -O4 -o test ./test.c
$ otool -tv ./test
./test:
(__TEXT,__text) section
_main:
0000000100000f70 movl $0x00000002,%eax
0000000100000f75 ret
There are also other optimization that can take place in case of the above snippets. Below are some of them that come to mind:
The first an most obvious is loop unrolling. Since number of iterations is known at run-time, compiler may decide to unroll the loop. Whether this optimization is applied or not depends on the architecture (i.e. some CPUs can "lock on your loop" and execute the code faster than its unrolled version, which also makes the code more cache friendly by using less space, avoiding extra µOP fusion stages, etc).
The second optimization that can literally speed things up like 50 times is using SIMD instruction (SSE, AVX etc). For example, GCC is very good at it (Intel must be too, if not better). I have verified that the following function:
uint8_t dumb_checksum(const uint8_t *p, size_t size)
{
uint8_t s = 0;
size_t i;
for (i = 0; i < size; ++i)
s = (uint8_t)(s + p[i]);
return s;
}
... is transformed into a loop where each step sums 16 values at once (i.e. as in _mm_add_epi8
) with an additional code handling alignment and odd (<16) iterations count. Clang, however, have totally failed at this last time I checked. So GCC may reduce your loop that way, too, even if number of iterations are not known.
And if I may I'd like to suggest you do not optimize your code unless you find it to be a bottleneck. Otherwise you may waste hell of a lot of time doing false and premature optimization.
I hope this answers your questions. Good Luck!