Optimizing C++/Writing efficient code/Performance worsening features

From Wikibooks, open books for an open world
Jump to navigation Jump to search

When compared to C, C++ has some features that worsen efficiency if used inappropriately.

Some of these features are nevertheless quite efficient and can be used liberally when useful. However, their increased cost should be avoided when the feature is not needed.

Other features are instead quite inefficient and so should be used sparingly.

In this section, guidelines are presented for the avoidance of C++ features that worsen performance.

The throw operator[edit | edit source]

Call the throw operator only when you want to notify a user of the failure of the current command.

Raising an exception has a very high cost when compared to a function call. It requires thousands of processor cycles. If an exception is raised only when a message is displayed on the user interface or written into a log file, there is a guarantee that it will not be performed too often without notice.

In contrast, if exceptions are made part of an algorithm, even if initially thought to be rare they may end up being performed too frequently.

virtual member functions[edit | edit source]

Define the destructor as virtual if and only if the class contains at least one other virtual member function or if the class might be derived from (i.e. the class is intended for use as a base class). With the exception of destructors, do not declare functions as virtual unless you intend to override them.

Classes that have virtual member functions occupy more storage space than those without. Instances of classes having at least one virtual member function occupy more space (typically, a pointer and possibly some padding) and their construction requires more time (typically, to set the pointer) than instances of classes without virtual member functions.

In addition, every virtual member function has a slower call time than an identical non-virtual member function.

virtual inheritance[edit | edit source]

Use virtual inheritance only when several classes must share the representation of a common base class.

For example, consider the following class definitions:

class A { ... };
class B1: public A { ... };
class B2: public A { ... };
class C: public B1, public B2 { ... };

With such definitions, every C class object contains two distinct class A objects, one inherited from the B1 base class, and the other from the B2 class.

This is no problem if class A has no non-static member variables.

If, instead, class A contains some member variables and you mean that such member variables are unique for every class C instance, you must use virtual inheritance, in the following way:

class A { ... };
class B1: virtual public A { ... };
class B2: virtual public A { ... };
class C: public B1, public B2 { ... };

This situation is the only case when virtual derivation is necessary.

The member functions of the class A are somewhat slower to call on a C class object if virtual inheritance is used.

Templates of polymorphic classes[edit | edit source]

Do not define templates of polymorphic classes.

In other words, don't use the "template" and the "virtual" keywords in the same class definition.

Every time a class template is instantiated a copy is made of all member functions used. If such classes contain virtual functions, even their vtable and RTTI data is replicated. This data bloats the code.

Use of automatic deallocators[edit | edit source]

Use a memory manager based on garbage-collection or a kind of reference-counted smart-pointer (like shared_ptr in the Boost library) only if you can prove its expediency for the specific case.

Garbage collection, or automatic reclamation of unreferenced memory, allows the programmer to leave out calls for memory deallocation and prevents memory leaks. However, one must implement garbage collection through a non-standard library since these features are not included in C++. In addition, code using garbage collection typically executes more slowly than code using explicit deallocation (when the delete operator is explicitly called). In addition, when the garbage collector runs the rest of the program doesn't. This makes execution of the program less deterministic.

The C++98 standard library contains only one kind of smart-pointer, auto_ptr, that is quite efficient. Other smart-pointers provided by non-standard libraries, such as Boost, will be provided by the C++11 standard library. Smart-pointers rely upon reference-counting but are less efficient than simple pointers. For example, shared_ptr is the standard Boost smart pointer. However, if the thread-safe version of Boost is used, the library has very poor performance for creation, destruction and copying of these pointers as it must guarantee mutual exclusion for these operations.

Usually, you should try to assign every dynamically allocated object to a single owner at design time. When such an assignment is difficult, for example if several objects tend to bounce the responsibility to destroy the object, it becomes expedient to use a reference-counted smart-pointer to handle the object.

The volatile modifier[edit | edit source]

Only variables that are changed asynchronously by hardware devices should be defined volatile.

The usage of the volatile modifier prevents the compiler from locating a variable in a register, even for a short time. This guarantees that all devices see the same variable, but slows down operations that access the variable.

volatile does not guarantee that other threads will see the same values, as it does not force the compiler to generate the necessary memory barrier and lock instructions. Therefore, read and write access to a volatile value may be made out of order even on the same CPU (important in the case of interrupts and signals) and especially out of order across the memory cache and bus to other CPUs. You must use a proper thread or atomic memory API or write machine code to guarantee the proper order of operations for safe threading.