Map from the C++ code to the equivalent C code

Map from the C++ code to the equivalent C code

This article compares C and C++ by comparing the C++ code and its equivalent C code. This comparison should give you a better feel of the performance differences between C and C++.

C++ Method Invocations

In the first article in this series we will look at the performance impact of C++ method invocations. This comparison will be carried out by first comparing C++ code and its C equivalent.

C++ Code

The following C code provides an equivalent implementation for the C++ code shown above. The C++ class has been mapped to a C structure.

C code

Analysis

This section analyses the C++ code and its C translation and identifies the performance impact.

C++ Method Invocation All C++ methods when translated to C end up with an additional parameter. This might appear to be a big performance overhead. In reality however, the code in C will also have to access the common data structure via an array index or some other mechanism.
Object Construction Whenever an object is constructed, C++ will invoke the constructor. Sometimes this might be an addition overhead. This overhead can be reduced by defining the constructor inline. In most cases however, the constructor is actually replacing a routine that would have been used to initialize the data structures in a conventional C program. If a program declares a lot of global objects, object construction can be a big overhead at program startup. C++ invokes constructors for all global objects before main() is called.
Object Destruction As you can see from the C code, whenever an object goes out of scope or is explicitly deleted, C++ invokes the destructor for the object. This overhead can be reduced by only defining destructors when they are really needed (i.e. some action is required when object is deleted). Inline destructors can also be used to reduce the overhead.
Static Access The C code above shows that static member functions and variables do not correspond to an instance of the object. Thus they are accessed without indirection of the object. This can be useful in defining methods which need C level function call conventions. One good use for static member functions is to implement interrupt service routines (ISRs). ISRs handlers typically need to be C type functions. In most implementations, C++ static functions can be directly used as ISR handlers.

Virtual Functions and Inheritance

This section presents the C++ code for a typical virtual function invocation scenario. This is then compared to the equivalent C code.

C++ code

C code

C code implementing the above C++ functionality is shown below. The code also includes compiler generated constructs like vtables. Virtual function access using vtables is also covered. (The presentation here has been simplified here to aid understanding).

Analysis

This section analyses the C++ code and its C translation and identifies the performance impact.

Object Construction Inheritance does increase the object construction overhead, as constructors for all the parent classes in the class hierarchy are invoked. There is the additional overhead of setting up vtable in the constructor.
Object Destruction Inheritance does increase the object destruction overhead, as destructors for all the parent classes in the class hierarchy are invoked. There is the additional overhead of setting up vtable in the destructor. Virtual destructors also increase the overhead of object destruction.
Virtual Function Invocation

Virtual function invocation is slightly more expensive than invoking a function through a function pointer. In many scenarios, intelligent compilers can use normal method invocation instead of a virtual function invocation.

In a well designed object oriented system, a virtual function call would typically replace a switch statement so virtual function invocation might actually be faster than conventional coding techniques. For example, a generic draw statement in a "C" based paint program would involve switching over the type of shape and then invoking the corresponding draw function. In C++, this logic will be replaced by a virtual function call.

In our experience we have found that poorly designed and excessive use of constructors and destructors reduces performance much more than virtual function calls.

Memory Overhead Using plain inheritance has no memory overhead. Inheritance with virtual functions however does introduce the following memory overhead:
  • A vtable array pointer is added to all classes that use virtual functions.
  • Global vtable arrays are declared for every call with virtual functions (this should be a very small overhead).
Locality of Reference In the current computing environment, processor speeds have increased considerably but memory access speeds haven't kept pace. In such a scenario, cache hit ratio of an application plays a very important role in determining application performance.

In general this turns out to be an advantage for programs written in C++. With C++ code and data locality of reference is much better than C, as all the class code manipulating object data is located together. Also all object data is located at one place. In C code and data are scattered all over the place. Thus a C++ program should offer a better locality of reference than a C program. In many cases this might more than compensate for the performance overhead of C++.

Multiple Inheritance and Virtual Base Classes In this article we have not covered multiple inheritance and virtual base classes. There is a significant increase in overhead due to these features. We would recommend that you stay away from using these features.

Explore More