Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do C++ compilers optimize repeated function calls?

Do compilers (generally or in particular) optimize repeated function calls?

For example, consider this case.

struct foo {
  member_type m;
  return_type f() const; // returns by value
};

The function definition is in one translation unit

return_type foo::f() const {
  /* do some computation using the value of m */
  /* return by value */
}

Repeated function calls are in another unit

foo bar;

some_other_function_a(bar.f());
some_other_function_b(bar.f());

Would the code in the second translation unit be converted to this?

foo bar;

const return_type _tmp_bar_f = bar.f();

some_other_function_a(_tmp_bar_f);
some_other_function_b(_tmp_bar_f);

Potentially, the computation f does can be expensive, but the returned type can be something very small (think about a mathematical function returning a double). Do compilers do this? Are there cases when they do or don't? You can consider a generalized version of this question, not just for member functions, or functions with no arguments.

Clarification per @BaummitAugen's suggestion:

I'm more interested in the theoretical aspect of the question here, and not so much in whether one could rely on this to make real world code run faster. I'm particularly interested in GCC on x86_64 with Linux.

like image 375
SU3 Avatar asked Feb 09 '17 01:02

SU3


People also ask

What is compiler optimization in C?

Compiler optimization is generally implemented using a sequence of optimizing transformations, algorithms which take a program and transform it to produce a semantically equivalent output program that uses fewer resources or executes faster.

Do compilers Optimise code?

Compilers are free to optimize code so long as they can guarantee the semantics of the code are not changed.

What optimization does GCC do?

The compiler optimizes to reduce the size of the binary instead of execution speed. If you do not specify an optimization option, gcc attempts to reduce the compilation time and to make debugging always yield the result expected from reading the source code.

Does GCC optimize code?

GCC has a range of optimization levels, plus individual options to enable or disable particular optimizations. The overall compiler optimization level is controlled by the command line option -On, where n is the required optimization level, as follows: -O0 . (default).


1 Answers

GCC absolutely optimizes across compilation units if you have Link Time Optimization on and the optimization level is high enough, see here: https://gcc.gnu.org/wiki/LinkTimeOptimization There is really no reason besides compilation time to not do both of these.

Additionally, you can always help the compiler along by marking the function with the appropriate attributes. You probably want to mark the function with the attribute const as follows:

struct foo {
  member_type m;
  return_type f() const __attribute__((const)); // returns by value
};

Take a look at GCCs documentation here to see which attribute is appropriate: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html

In a more general sense, this is very easy for a compiler to detect. It actually performs transformations that are much less obvious. The reason why Link Time Optimization is important, though, is that once GCC has generated actual machine code, it will not really know what is safe at that point to do. Your function could, for example, modify data (outside your class) or access a volatile variable.

EDIT:

GCC most definitely can do this optimization. With this code and the flags -O3 -fno-inline:

C++ code:

#include <iostream>

int function(int c){
  for(int i = 0; i != c; ++i){
    c += i;
  }
  return c;
}

int main(){
  char c;
  ::std::cin >> c;
  return function(c) + function(c) + function(c) + function(c) + function(c);
}

Assembly Output:

4006a0: 48 83 ec 18             sub    rsp,0x18
4006a4: bf 80 0c 60 00          mov    edi,0x600c80
4006a9: 48 8d 74 24 0f          lea    rsi,[rsp+0xf]
4006ae: e8 ad ff ff ff          call   400660 <_ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_RS3_@plt>
4006b3: 0f b6 7c 24 0f          movzx  edi,BYTE PTR [rsp+0xf]
4006b8: e8 13 01 00 00          call   4007d0 <_Z8functioni>
4006bd: 48 83 c4 18             add    rsp,0x18
4006c1: 8d 04 80                lea    eax,[rax+rax*4]
4006c4: c3                      ret    
4006c5: 66 66 2e 0f 1f 84 00    data32 nop WORD PTR cs:[rax+rax*1+0x0]
4006cc: 00 00 00 00 

It does, however, fail to do this when the function is in a separate compilation unit and the -flto option is not specified. Just to clarify, this line calls the function:

call   4007d0 <_Z8functioni>

And this line multiplies the result by 5 (adding together five copies):

lea    eax,[rax+rax*4]
like image 96
Nick Apperson Avatar answered Sep 17 '22 15:09

Nick Apperson