|
|
|
|
||||
By default, exception-handling is turned on. When exception-handling
is turned off, exception-handling constructs such as throw
,
catch
, and try
are rejected and flagged
by error messages. When exception-handling is turned on, KAI C++
accepts and compiles exception-handling constructs. The command-line
option for turning off exception-handling is shown below.
--no_exceptions
Exception-handling usually incurs a slight performance penalty, even if exception-handling constructs are not used. The reason is that whenever a call to a function is made, there is potential for an exception to be thrown "over the head" of the caller, which requires destroying all local objects. This implies that the control-flow of the program is inherently more complex. At the highest levels of optimization, the extra overhead is usually small. We measured it to be about 10% for a numerics-intensive benchmark.
General point: Some implementations of C++ claim to have "zero overhead" exceptions. This is impossible as exceptions inherently add new paths of control flow to a program. What they really mean by "zero overhead" is "prepaid overhead". (I like the vendor who advertises "zero overhead", but has a switch to turn off exceptions to enable a "faster smaller executable".)
The way to find out what the overhead is for exception handling is to benchmark your own application with it turned on and off. In our experience, the overhead is usually in the 1-10% range.
If you have exceptions enabled, and your code does not use exceptions, and does not use classes that throw exceptions, there is still a performance penalty to be paid. Every local (auto) object that has a nontrivial destructor is added to an "exception stack" during normal execution. If over the local object's lifetime, there are no non-inline calls, the optimizer (usually) removes the push and pop of this object onto the "exception stack". This keeps the performance penalty for exception handling fairly low, especially in inner loops, where the optimizer is at its best.
The one area in which some programmer intervention pays off is with regard to code bloat. Inline try blocks and inline throws can cause significant code bloat. We recommend that all try blocks and throw expressions be put out of line. For example, consider the following fragment:
template<class T> class Array { public: T operator[]( int k ) { if( k>=my_size ) throw out_of_range("Array::operator[]"); return my_array[k]; } private: size_t my_size; T * my_array; ... };
The operator[] is implicitly inline because its definition is coded within the class. It is desirable here to make the normal execution path fast and small in terms of code size. But inlining the exceptional path causes code bloat, as every call site for operator[] will duplicate the code for throwing an exception. The solution is to partition the logic of operator[] into two parts: an inline part for the normal case, and an out-of-line part for the exceptional case. Further improvement can be gained by making the out-of-line part a non-template if possible, so that it is not replicated for each distinct T. Below is the repartitioned code:
class ArrayBase { // Not a template class protected: void index_error(); // Out of line }; void ArrayBase::index_error() { throw out_of_range("Array::operator[]"); } template<class T> class Array: private ArrayBase { public: T operator[]( int k ) { if( k>=my_size ) index_error(); return my_array[k]; } private: size_t my_size; T * my_array; ... };
This technique is used in the KAI C++ standard class library itself. Instead of directly invoking throw, it invokes an out-of-line static method __throw that does the work.
We recommend factoring out template-independent code from templates not just for exceptions, but for all code. Significant reductions in code size and compilation time can be accomplished. For example, KAI C++ version 3.2 has a refactored <map> class that compiles 5x faster and generates code 3x smaller than the previous (unfactored) one did.