I’m not sure what it is about inline that makes people abuse it. Maybe its the assumption that its an easy way to increase performance or that it saves time when writing code. Unfortunately, misusing inline often causes more problems than it solves.

The only legitimate use of inline is to increase performance. If you’re calling a function a lot, and the overhead of the actual function call is a significant portion of the time spent, then inline is appropriate to use. The overhead doesn’t include the actual body of the function, but the prologue and epilogue that set up and tear down the stack. So if you’re trying to increase the performance of a function itself, inlining won’t help you.

For some reason, some people think inlining is a harmless way to get performance increases. That is, just inline whatever you feel like and you’ll obtain a performance boost, with no adverse side effects. If only that were true.

Problem number one with inlining is maintenance. Anytime that you modify an inline function, anyone who even includes that header file has to be recompiled and linked, regardless if they use the inlined function or not. If you’re on a project of any size, that could turn out to be a lot of files. As a result, inlining increases build times, thus lengthening development cycles.

Problem number two is readability. Most engineers realize that well designed classes encapsulate and hide the implementation. In a way, inlining breaks that by showing implementation in the public interface. Hopefully, most well written classes are used (read) more often than they are modified (write). If I’m trying to use your class, I scan the public methods. If you have inlined functions in your public interface, that’s a lot more code that I have to climb over to get the information that I want.

The other part of readability comes in when debugging. I’m not sure why, but when people inline functions they like to put it all on one line, as if the compiler charged per line used. If someone else comes through and tries to debug the inlined function, its impossible. They cannot set a breakpoint where they want, nor can they accurately step through the statements. Personally, this is one of the more aggravating problems of inlining, although its not inherently caused by inlining.

Also, by putting inlines in the header, you separate that part of the implementation from the rest of the implementation. Most people go to the implementation file (.cpp) when they want to figure out how things work. But then they have to start switching between the header and implementation files, if there are inlines.

Problem number three is code size. Don’t forget what inlining actually does. It copies that code into the places where the function would normally be called. In some cases, it doesn’t increase the code size by much. But for large functions or functions that are used all over the place, this can result in a significant increase in the executable size. That means more paging out to disk, and a longer download if you are distributing over the web. That might be fine if you’re getting an actual performance increase.

Of course, this all assumes inlining will get you better performance. Most of the time it won’t. For most accessor functions there is little or no overhead to calling that function. On the PowerPC functions that do not call other functions can operate in the “red zone,” which means they don’t have set up or tear down a stack frame. So there is very little performance increase in that case.

Furthermore, most inlines aren’t called enough times to save any real time. By real time, I mean time the user will actually notice. Remember, inlines only remove the time required to set up and tear down a stack frame. The time to do that usually isn’t that significant unless you’re calling the function thousands of times in a very short interval. If you just call the function fifty times when the user selects a menu item, sorry, no one will notice that its inlined. You are not giving the user any benefits. But you are inconveniencing yourself and other developers.

There does seem to be one popular reason to use inlines that I do not understand. That is, to save the developer time when writing code. Yep, apparently some people can’t be bothered to switch to the implementation file when writing functions, so they will simply inline the function where they declare it. They are not even attempting to use inlines for the purpose they were invented, but do managed to foist all the disadvantages of inlining on the function. It is a lose-lose situation.

Worst of all are virtual inlines. That is, functions that are declared both virtual and inline. I’m not sure how the authors of these functions actually expect these to work. Inline is a suggestion to the compiler, not a command: just because you ask, does not mean you will receive. A compiler cannot inline a virtual function, unless it knows its type is static. e.g.


class Foo {
inline virtual void bar(void) {
std::cout << "hello world" << std::endl;
}
};

...

Foo foo;
foo.bar();

In this case, bar could actually be inlined because the compiler knows what type foo will be at runtime. However, in most cases, virtual functions are used like this:


class Foo {
inline virtual void bar(void) {
std::cout << "hello world" << std::endl;
}
};

void CallBar(Foo* foo) {
foo->bar();
}

In this case, the more common case, the compiler can’t inline. foo could be a Foo or any other derived class that overrides bar(). Once again, all the inconveniences of inline, without any of the advantages.

Don’t get me wrong, I’m not trying to say never use inlines. There is a legitimate use for them. You use them when a function is called enough times that the overhead becomes significant.

There are certain guidelines I follow when using inlines.

First, I only use inlines for performance reasons, and only when a performance tool, such as Shark, indicates a problem. Some engineers just start inlining functions in a module when they think that module is slow. That makes no sense.

Think of it this way: when you get a crashing bug, what do you do? Do you start randomly start changing code in the module where you think the crash is happening? Or do you use a debugger to figure out where the crash happens, and only change code after you’ve determined the cause? I think just about everyone uses a debugger to determine the cause first. In the same way, performance problems are just another bug. But instead of using a regular debugger to find the problem, you use a performance debugger, such as Shark, to find the problem. Then you change the code.

Second, when I do use inlines, I don’t put them inside the class declaration, but at the bottom of the header file. Like so:

class Foo {
inline void bar(void);
};

inline void Foo::bar(void) {
std::cout << "hello world" << std::endl;
}

That way, the implementation is inlined, but out of the way so the public interface is easier to read.

Third, I try to determine if I can change my algorithm to simply not call the function as often. It is frequently a more drastic change to make, but it also frequently is a more drastic performance increase than a simple inline.

In the end, inlining can get you performance increases. Just not as often as most people think, and not without some complications.