C++ — Choice for Function Performance Optimization, inline

HyunsuYu
More Deeper C++ Programming Language
6 min readJul 2, 2024

What is inline

An inline specifier is a word that tells the compiler that a function needs to save the function code once and then try to generate the inline call code rather than call it through a conventional function call mechanism

At this time, whether a function is an inline function exists in both implicit and explicit ways

Advantages and disadvantages of the inline function

Let’s look back on what usually happens when you call a function

First, you must save information about the context of the code you’ve been running before proceeding to another function’s code when you call a function. Only then can you complete all the codes you want to call and return to continue with what you were doing before.
As a result, we store information about the context that we were running in the existing function’s Stack Frame. This Stack Frame corresponds to a Stack, which is commonly referred to as Call-Stack, and it can be called differently depending on the operating system. Simply put, a function that has been executed will generate one Stack Frame corresponding to it

Once you’ve saved the previous context, you’re now starting to read the code for the new function that was called. Naturally, a new Stack Frame will be created, and at a lower level, Jump Instruction will have to be done when you move from one function to another. Depending on what situation that function is in, you might need to pay more. For example, let’s say that the child class took the overloaded virtual functions from the parent class and overridden them. Calling that function will require the process of overloading resolution and the process of deciding whether to call the parent class or child class functions from Virtual-Table

After processing the called function, it’s time to return to the previous function. You’ll need to remove the Stack Frame of the called function, read the Stack Frame data of the previous function, and return the various registers to the previous situation. This shift of context is called Context-Switch, a process that is briefly described in this post, but is actually more expensive than you can imagine

In this way, the typical function calls are often performed in the invisible beyond our imagination. This is a cost, which poses a risk of performance and, in the worst case, exceptional exceptions, including the very famous Stackoverflow

The inline function is one of the optimization techniques to overcome the shortcomings mentioned above. The idea of the inline function is very simple. The large cost described above is essentially incurred to ‘call’ a function. To avoid this, inline pastes the contents of the function you want to call at all to the location where you called it. This eliminates all the necessary processing to call the function. Of course, the result is that it can take a little longer to compile, but it can improve runtime performance

Implicict inline

In the case of an implicit inline function, if the declaration of the class includes even the complete definition of the function, the function is implicitly designated as an inline function candidate

class Person
{
private:
int m_age;

public:
int GetAge() const { return m_age; }
};

For the above example code, Person::GetAge() does not explicitly declare the inline specifier, but it is designated as a candidate for the inline function because the declaration part of the Person class contains a complete definition with the declaration of the method

Note that it has only become a ‘candidate’ of the inline function and can actually be inline only if all the conditions that can be inline are met additionally

Explicit inline

In the case of explicit inline functions, if the inline specifier is declared in all functions, it is designated as a candidate for the inline function

inline int GetUID(const std::string& name);

If an inline specifier is explicitly declared, as in the example code above, the function is designated as a candidate for the inline function

It is important to note that, as mentioned in implicit inline, it has only become a ‘candidate’ of the inline function and can actually be inline if all the conditions that can be inline are met additionally

Conditions for a function to actually be inlined

A simple rule in which a function is designated as a candidate for an inline function is as above

However, for a function nominated for an inline function to actually be inline, that function must satisfy some additional conditions
To sum up the conditions, ‘the slightest complex function will never be inline

  • The content of the function should not contain a loop, and if it does, it should not be complicated
  • It should not take the form of a recursive function, and if it is a recursive function, it should be a shallow recursive function
  • Virtual function call (polymorphic) format not taken
  • Only one body of a function definition must exist
  • All compilation units must be declared in the same way

In a way, these are very simple conditions. Of course, these conditions may not always apply because they are compiler-dependent

However, even if a function is designated as an inline candidate and satisfies the above conditions, there may be cases where it is not actually possible even if it is convinced that it must be inline unconditionally

inline int GetUID(const std::string& name);

int (*GetUIDPtr)(const std::string& name) = GetUID;

void Sample()
{
std::string name{"John"};

GetUID(name);

GetUIDPtr(name);
}

Suppose that in the above example code, “inline int GetUID(conststd:string&name)” is definitely an inline function
In that case, “GetUID(name);” is definitely inline, but “GetUIDPtr(name)” is definitely not inline

The difference is simple: functions called through a function pointer that takes the address of an inline function are not inline
The reason is also very simple if you think about it a little bit, but an inline function is to paste the body of a function into the part where the function is called. In other words, the form of the function disappears, and only the body remains. But how can a function pointer get a function pointer when there is only the body

It might be naive to see this result and think that you just have to make sure that you are not the only one calling through the function pointer of the inline function.
This is because, among the vast mechanisms and libraries of C++, there are more things that a user-defined function points to and processes with a function pointer regardless of whether it is an inline function or not

Also, the caveat when inline is that debugging can become difficult
Obviously I’d like to break down the calling part of a function and debug it, but if the calling of that function is inline, how on earth do you break down a function that doesn’t even exist in the end?

Conclusion

inline is very useful, but the available situations are limited, and its disadvantages are relatively clear. However, if you use it well, it is clearly a good optimization technique. Please try to optimize your program through a lot of research and practice

If you want to know more about tips on using inline in a visual studio environment, I recommend you to read one of my posts, the following:

Materials recommended to read

--

--

HyunsuYu
More Deeper C++ Programming Language

As a game developer with Unity and C# as the main players, I've been working on a number of game development projects and side projects