C++ tip: Considering the runtime cost of virtual methods

Topics: C/C++
Technologies: C++

Object-oriented design favors class hierarchies with general base classes and specific subclasses. Virtual methods provide general implementations on the base class, overridden for specific implementations by subclasses. But how does this coding style affect runtime performance? This article does a quick benchmark to illustrate the cost of "virtual" on a method.

Virtual methods

C++'s "virtual" keyword marks a method that may be overridden by a subclass. During API design, it's common to mark nearly everything "virtual" on base classes to allow maximum flexibility in subclassing. This is such a common approach that it's easy to forget there's a runtime cost to "virtual".

What is the runtime cost of marking a method "virtual"?

To see the cost let's do a simple benchmark that measures the runtime of a "get" method that just retrieves some internal object value. Such "get" methods are common in container classes, like the STL's <vector>, <list>, <map>, and others.

We'll benchmark three variations:

  • Direct access to an object's state value, without a method call. This is usually poor design, but it's a good benchmark baseline.
  • Non-virtual inlineable method access to the state value using a method call.
  • Virtual method access to the state value using a method call.

Results

The plot below shows the three variations benchmarked using g++, clang++, and icpc with maximum compiler optimizations. The vertical axis is run time and lower is better.

The axis numbers are intentionally omitted since the exact run time will vary based on the processor and "get" method. What matters is general notions of "high" and "low" for the three variations.

The inlineable non-virtual method call clearly took the same run time as directly accessing the object's state value. The virtual method call was much more expensive.

Observations

When a method is virtual, the implementation isn't known until run time. For each runtime "get" call, the proper implementation may be that of the base class or one of its subclasses, depending upon the type of object at hand. While this runtime method dispatch gives object-oriented programming its flexibility, it has overhead.

Today's compilers try to inline all methods with definitions found in the ".h" file. But when a method is marked "virtual", inlining is impossible. The proper method body isn't known until runtime. For simple "get" methods, the cost of a full method call to a virtual method exceeds the cost of the "get" body.

In the plot above, the non-virtual method call was inlined and therefore took the same amount of time as directly accessing the object's state value. The virtual method took about 4x longer. Your mileage will vary depending upon what your "get" actually does.

Conclusions

Be careful with "virtual". When designing an API for which performance matters (and it should almost always matter), the default design choice should be to not mark methods "virtual". This enables methods to be inlined for better performance. Only mark a method "virtual" if there is a demonstrated need for a subclass to override the method.

Comments

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options

Nadeau software consulting
Nadeau software consulting