21

Sometimes compilers inline function calls. That means that they move the code of the called function into the calling function. This makes things slightly faster because there's no need to push and pop stuff on and off the call stack.

So my question is, why don't compilers inline everything? I assume it would make the executable notably faster.

The only reason I can think of is a significantly larger executable, but does it really matter these days with hundreds of GB of memory? Isn't the improved performance worth it?

Is there any other reason why compilers don't just inline all function calls?

19
  • 19
    IDK about you, but I don't have hundreds of GB of memory just lying around.
    – Ampt
    Aug 28, 2014 at 13:36
  • 2
    Isn't the improved performance worth it? For a method that'll run a loop 100 times and crunch some serious numbers, the overhead of moving 2 or 3 arguments to CPU registers is nothing.
    – Doval
    Aug 28, 2014 at 13:38
  • 6
    You are overly generic, does "compilers" mean "all compilers" and does "everything" mean really "everything"? Then then the answer is simple, there are situations where you simply can't inline. Recursion comes to mind. Aug 28, 2014 at 13:38
  • 17
    Cache locality is a way much more important than tiny function call overhead.
    – SK-logic
    Aug 28, 2014 at 14:02
  • 3
    Does performance improvement really matter these days with hundreds of GFLOPS of procesing power?
    – mouviciel
    Aug 28, 2014 at 15:04

6 Answers 6

27

First note that one major effect of inline is that it allows further optimizations to be made at the call site.

For your question: there are things which are difficult or even impossible to inline:

  • dynamically linked libraries

  • dynamically determined functions (dynamic dispatch, called through function pointers)

  • recursive functions (tail recursion can)

  • functions for which you don't have the code (but link time optimization allow this for some of them)

Then inlining has not only beneficial effects:

  • bigger executable means more disk place and bigger load time

  • bigger executable means increase of cache pressure (note that inlining small enough functions such as simple getters may decrease the executable size and the cache pressure)

And finally, for functions which takes a non trivial time to execute, the gain just doesn't worth the pain.

2
  • 3
    some recursive calls can be inlined (tail calls), but all can be transformed into iteration if you optionally add a explicit stack Aug 28, 2014 at 13:51
  • @ratchetfreak, you can also transform some non tail recursive call in to tail one. But that's for me in the realm of the "difficult" one (especially when you have co-recursive functions or have to determine dynamically where to jump to simulate the return), but that's not impossible (you just put in place a continuation framework and considering that present it becomes easier). Aug 28, 2014 at 14:07
13

A major limitation is runtime polymorphism. If there is a dynamic dispatch happening when you write foo.bar() then it is impossible to inline the method call. This explains why compilers don't inline everything.

Recursive calls cannot be easily inlined either.

Cross module inlining is also difficult to perform for technical reasons (incremental recompilation would be impossible for ex)

However, compilers do inline a lot of things.

3
  • 3
    Inlining through a virtual dispatch is very difficult, but not impossible. Some C++ compilers are able to do it under certain circumstances.
    – bstamour
    Aug 28, 2014 at 16:28
  • 2
    ... as well as some JIT compilers (devirtualization).
    – Frank
    Aug 28, 2014 at 16:36
  • @bstamour Any half-decent compiler of any language with appropriate optimisations on will statically dispatch, i.e. devirtualise, a call to a declared-virtual method on an object whose dynamic type is knowable at compile-time. This can facilitate inlining if the devirtualisation phase occurs before the (or another) inlining phase. But this is trivial. Was there something else you meant? I don't see how any actual "Inlining through a virtual dispatch" can be achieved. To inline, one must know the static type - i.e. devirtualise - so the existence of inlining means there is no virtual dispatch Oct 4, 2016 at 20:07
9

First, you cannot always inline, e.g. recursive functions might not be always inlinable (but a program containing a recursive definition of fact with just a printing of fact(8) could be inlined).

Then, inlining is not always beneficial. If the compiler inlines so much that the result code is big enough to have its hot parts not fitting in e.g. the L1 instruction cache, it might be much slower than the non-inlined version (which would easily fit the L1 cache)... Also, recent processors are very fast at executing a CALL machine instruction (at least to a known location, i.e. a direct call, not a call thru pointer).

At last, full inlining requires a whole program analysis. This might not be possible (or is too costly). With C or C++ compiled by GCC (and also with Clang/LLVM) you need to enable link-time optimization (by compiling and linking with e.g. g++ -flto -O2) and that takes quite a lot of compilation time.

2
7

Surprising though it may seem, inlining everything doesn't necessarily reduce execution time. The increased size of your code can make it difficult for the CPU to keep all your code in its cache at once. A cache miss on your code becomes more likely and a cache miss is expensive. This is made far worse if your potentially inlined functions are large.

I've had noticeable performance improvements from time to time by taking large chunks of code marked as 'inline' out of header files, put them into the source code, so the code is only in one place rather than at every call site. Then the CPU cache is used better and you also get better compile time...

2
  • this seems to merely repeat points made and explained in a prior answer that was posted an hour ago
    – gnat
    Aug 28, 2014 at 16:50
  • 1
    What caches? L1? L2? L3? Which one is more important? Aug 28, 2014 at 17:39
1

Inlining everything would not mean just increased disk memory consumption but also increased internal memory consumption which is not that plentiful. Remember that code also relies in memory in code segment; if a function is called from 10000 places (say the ones from standard libraries in a fairly large project), then the code for that function occupies 10000 times more internal memory.

Another reason might be the JIT compilers; if everything is inline then there are not hot spots to be dynamically compiled.

1

One, there are simple examples where inlining everything will work out very badly. Consider this simple C code:

void f1 (void) { printf ("Hello, world\n"); }
void f2 (void) { f1 (); f1 (); f1 (); f1 (); }
void f3 (void) { f2 (); f2 (); f2 (); f2 (); }
...
void f99 (void) { f98 (); f98 (); f98 (); f98 (); }

Guess what inlining everything will do to you.

Next, you make the assumption that inlining will make things faster. That's the case sometimes, but not always. One reason is that code that fits into the instruction cache runs a lot faster. If I call a function from 10 places, I'll always run code that is in the instruction cache. If it is inlined, then the copies are all over the place and run a lot slower.

There are other problems: Inlining produces huge functions. Huge functions are a lot harder to optimise. I've got considerable gains in performance critical code by hiding functions into a separate file to prevent the compiler from inlining them. As a result, the generated code for these functions was much better when they were hidden.

BTW. I don't have "hundreds of GBs of memory". My works computer doesn't even have "hundreds of GBs of hard drive space". And if my application where "hundreds of GBs of memory", it would take 20 minutes just to load the application to memory.

Not the answer you're looking for? Browse other questions tagged or ask your own question.