75

Suppose I have the following C code:

int i = 5;
int j = 10;
int result = i + j;

If I'm looping over this many times, would it be faster to use int result = 5 + 10? I often create temporary variables to make my code more readable, for example, if the two variables were obtained from some array using some long expression to calculate the indices. Is this bad performance-wise in C? What about other languages?

Elliot Gorokhovsky
  • 3,610
  • 2
  • 31
  • 56
  • 48
    Optimizing compiler will change that code to become effectively: `int result = 15 ;` – 2501 Nov 15 '14 at 19:10
  • 13
    Compiler will optimize your code. It's more productive to focus on matters such as (part of) a calculation repeated within a loop that would be better done before the loop begins. – Weather Vane Nov 15 '14 at 19:10
  • 5
    I think that he means any temporary variables, i.e.: is using a = b + c; d = a + e; slower than using a = b + c + d + e; it may potentially use more memory if done in ways the compiler can't optimise, but it shouldnt be slower. best focus or work productivity unless it's a commercial and critical performance code. – bandybabboon Nov 15 '14 at 23:05
  • 1
    @WeatherVane although most compilers would do that as well, at least to some degree. In general I think it'd be better to focus on code maintainability rather than microoptimisations like that. – FireFly Nov 16 '14 at 02:42
  • 1
    As the several answers suggest, there is rarely a performance penalty from using temporaries in you code. There is almost never a performance advantage to writing "long chain polymer" code that combines multiple functions into a single statement vs the simpler, easier to understand, easier to debug approach of doing the code in stages with the result of each stage assigned to a temp. – Hot Licks Nov 16 '14 at 04:03
  • I'm going to guess no whenever the compiler can "optimize" the values. However, if reading from a file, there may be a difference. – Ryan Dougherty Nov 16 '14 at 17:10
  • 6
    @PeteBecker I'm afraid that isn't a productive suggestion. It is quite easy to try something like this and get the wrong impression because you happen to have picked (or failed to pick) a case which is an exception to the general rule. Without a clear understanding of how a compiler works, just testing a few cases should not in any way convince you that it is true for all cases; making such generalisations can be very risky and often leads to error. – Jules Nov 16 '14 at 19:18
  • To add a couple of $0.01: a) an optimizing compiler (like gcc) will identify the relationship of (many of) the values calculated and/or used in a piece of code; irrespective if they're constants or not. b) On the assembler level, operations usually store their result in a register which can then be used as input to the next operation; since there are normally more variables involved than HW registers available the compiler will have to decide after each atomic operation which values to keep in a register and which to store to RAM. This way it kind of introduces its own temporary – JimmyB Nov 17 '14 at 12:56
  • You can compile it both ways and then use time(1) to see time-based performance differences; you probably won't notice any unless you have a LOT of stack allocation and deallocation of little variables like that. – Gophyr Nov 17 '14 at 12:56
  • variable (`registerX`) during calculations. c) While the compiler is mapping variables, intermediates, and constants to physical registers anyway, it usually tries to do so in some optimized way, which will often eliminate any explicit temporary variable assignments present in the high level language code. So, when optimization is enabled when compiling, there will usually be no difference in the assembler/binary code generated. – JimmyB Nov 17 '14 at 12:59

5 Answers5

85

A modern optimizing compiler should optimize those variables away, for example if we use the following example in godbolt with gcc using the -std=c99 -O3 flags (see it live):

#include <stdio.h>

void func()
{
  int i = 5;
  int j = 10;
  int result = i + j;

  printf( "%d\n", result ) ;
}

it will result in the following assembly:

movl    $15, %esi

for the calculation of i + j, this is form of constant propagation.

Note, I added the printf so that we have a side effect, otherwise func would have been optimized away to:

func:
  rep ret

These optimizations are allowed under the as-if rule, which only requires the compiler to emulate the observable behavior of a program. This is covered in the draft C99 standard section 5.1.2.3 Program execution which says:

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced (including any caused by calling a function or accessing a volatile object).

Also see: Optimizing C++ Code : Constant-Folding

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 2
    How can I see what assembly my C code will generate? – syntagma Nov 15 '14 at 19:11
  • @REACHUS I added a link to the godbolt example where you can see the assembly – Shafik Yaghmour Nov 15 '14 at 19:12
  • Thanks but what command line tool can I use, does GCC provide any flag for it? – syntagma Nov 15 '14 at 19:14
  • 3
    @REACHUS, under `gcc` use `gcc -S -o asm_output.s app.c` – David Ranieri Nov 15 '14 at 19:14
  • @REACHUS under either `gcc` or `clang` you would use the `-S` to generate assembly. – Shafik Yaghmour Nov 15 '14 at 19:17
  • 2
    I don't think this addresses the question. The example given is simple, but I think the part about “if the two variables were obtained from some array using some long expression to calculate the indices” is more important here, and that can't be optimized away, or can it? – Arturo Torres Sánchez Nov 16 '14 at 00:47
  • 1
    @ArturoTorresSánchez variables are just a human concept. There are no really variables. What the compiler sees are references to expressions and then the assembly code they are equivalent. When someone asks about variable overhead, really means the memory overhead of the result assembly instructions of that references (such as register/memory read/writes). "Erasing" a variable means if the compiler was smart enough to recognize that there is no reason to reload that "reference" again from memory, since the data is in another reference reachable by it in this context... (1) – Manu343726 Nov 16 '14 at 13:33
  • 1
    @ArturoTorresSánchez ... (The array element in this case). And nowadays, compilers are very proficient at global optimizations and expression folding (See the answer bellow about SSA form). – Manu343726 Nov 16 '14 at 13:34
  • I think no example is easier for a compiler to optimize. Perhaps is easier for a human to predict, but no more. – Luis Colorado Nov 17 '14 at 12:13
29

This is an easy task to optimize for an optimizing compiler. It will delete all variables and replace result with 15.

Constant folding in SSA form is pretty much the most basic optimization there is.

usr
  • 168,620
  • 35
  • 240
  • 369
13

The example you gave is easy for a compiler to optimize. Using local variables to cache values pulled out of global structures and arrays can actually speed up execution of your code. If for instance you are fetching something from a complex structure inside a for loop where the compiler can't optimize and you know the value isn't changing, the local variables can save quite a bit of time.

You can use GCC (other compilers too) to generate the intermediate assembly code and see what the compiler is actually doing.

There is discussion of how to turn on the assembly listings here:Using GCC to produce readable assembly?

It can be instructive to examine the generated code and see what a compiler is actually doing.

Community
  • 1
  • 1
steven smith
  • 1,519
  • 15
  • 31
  • This is the more useful answer. The others went into the constant-folding part of things, rather than the local-copy of a memory location point. Assigning globals and array elements to locals is often helpful when the compiler can't prove that input arrays don't overlap, or that some unknown function being called doesn't can't make changes to the array. This often stops the compiler from reloading the same memory location multiple times. – Peter Cordes Aug 05 '15 at 02:08
10

While all sorts of trivial differences to the code can perturb the compiler's behavior in ways that mildly improve or worsen performance, in principle it it should not make any performance difference whether you use temp variables like this as long as the meaning of the program is not changed. A good compiler should generate the same, or comparable, code either way, unless you're intentionally building with optimization off in order to get machine code that's as close as possible to the source (e.g. for debugging purposes).

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 3
    The point about "as long as the meaning of the program is not changed" is key. There are many cases where two ways of writing a program will have slight semantic differences that might not matter to a programmer, but would require a compiler to generate much less efficient code for one than would be needed for the other. – supercat Nov 15 '14 at 20:42
  • 1
    Exactly what supercat said: the compiler can't always prove that two pointers / arrays don't overlap, or that a function call can't change the contents of memory. Thus, it sometimes is forced to generate multiple loads of the same location, but using `int a = arr[i]` would let the compiler keep the value in a register across function calls and writes through other pointers. – Peter Cordes Aug 05 '15 at 02:10
  • I agree completely with the points made by supercat and Peter Cordes. – R.. GitHub STOP HELPING ICE Aug 05 '15 at 02:12
5

You're suffering the same problem I do when I'm trying to learn what a compiler does--you make a trivial program to demonstrate the problem, and examine the assembly output of the compiler, only to realize that the compiler has optimized everything you tried to get it to do away. You may find even a rather complex operation in main() reduced to essentially:

push "%i"
push 42
call printf 
ret

Your original question is not "what happens with int i = 5; int j = 10...?" but "do temporary variables generally incur a run-time penalty?"

The answer is probably not. But you'd have to look at the assembly output for your particular, non-trivial code. If your CPU has a lot of registers, like an ARM, then i and j are very likely to be in registers, just the same as if those registers were storing the return value of a function directly. For example:

int i = func1();
int j = func2();
int result = i + j;

is almost certainly to be exactly the same machine code as:

int result = func1() + func2();

I suggest you use temporary variables if they make the code easier to understand and maintain, and if you're really trying to tighten a loop, you'll be looking into the assembly output anyway to figure out how to finesse as much performance out as possible. But don't sacrifice readability and maintainability for a few nanoseconds, if that's not necessary.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Scott
  • 1,179
  • 7
  • 17
  • 1
    The solution is to look at the compiler output for functions that operate on parameters, rather than `main()` operating on compile-time constants. e.g. here's a simple example of summing a float array, with gcc asm output from godbolt: http://goo.gl/RxIFEF – Peter Cordes Nov 10 '15 at 02:28
  • I believe that's what I said: "you'd have to look at the assembly output for your particular, non-trivial code". ;) – Scott Nov 10 '15 at 22:08
  • 1
    I was trying to say that you can put pretty trivial code in a function, and see the asm for it. (I think this turned into an argument over the definition of "trivial", which isn't what I intended...). Your example used a function call to generate `i` and `j`. My example would be to make `i` and `j` function *parameters*, so they'd be just sitting there in registers at the start of the code for the function. – Peter Cordes Nov 10 '15 at 23:03