Optimizing C++/Writing efficient code/Performance worsening features
With respect to C language, C++ has some features, that worsen efficiency if used inappropriately.
Some of them are anyway rather efficient, and therefore you can use them liberally when they are useful, but you should also avoid to pay their cost when you don't need them.
Other features are instead quite inefficient, and so you should use them sparingly.
In this section, guidelines are presented to avoid the costs of C++ features that worsen performance.
Contents |
The throw operator [edit]
Call the throw operator only when you want to notify a user of the failure of the current command.
The raising of an exception has a very high cost compared to a function call. It requires thousands of processor cycles. If you perform this operation only when a message is displayed on the user interface or written into a log file, you have the guarantee that it will not be performed too often without notice.
Instead, if exception raising is performed for algorithmic purposes, even if such operation is initially thought to be performed rarely, it may end up being performed too frequently.
virtual member functions [edit]
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. Except for destructors, do not declare functions as virtual unless you intend to override them.
Classes that have some virtual member functions occupy more storage space than those classes without them. 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 the 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]
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 not a problem if class A has no non-static member variables.
If instead such 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]
Do not define templates of polymorphic classes.
In other words, don't use the "template" and the "virtual" keywords in the same class definition.
Class templates, every time they are instantiated, generate a copy of all the member functions used, and if such classes contain virtual functions, even their vtable and RTTI data is replicated. This data bloats the code.
Use of automatic deallocators [edit]
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, garbage collection typically causes slower execution than that of explicit deallocation (when the delete operator is explicitly called). In addition, when the garbage collector runs the rest of the program is stopped, therefore increasing the variance of the time taken by the commands.
The C++98 standard library contains only one smart-pointer kind, auto_ptr, that is quite efficient. Other smart-pointers provided by non-standard libraries, like Boost, will be provided by the C++0x standard library. The smart-pointer is based on reference-count but is less efficient than a simple pointer. 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 since it must guarantee mutual exclusion for these operations.
Usually, you should try to assign every dynamically allocated object to an 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]
Define volatile only those variables that are changed asynchronously by hardware devices.
The usage of the volatile modifier prevents the compiler to allocate a variable in a register, even for a short time. This guarantees that all the devices see the same variable, but slows down much the operations that access this 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 accesses 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.