Cost of Throwing C++0X Exceptions

Cost of throwing C++0x exceptions

#include <iostream>
#include <stdexcept>

struct SpaceWaster {
SpaceWaster(int l, SpaceWaster *p) : level(l), prev(p) {}
// we want the destructor to do something
~SpaceWaster() { prev = 0; }
bool checkLevel() { return level == 0; }
int level;
SpaceWaster *prev;
};

void thrower(SpaceWaster *current) {
if (current->checkLevel()) throw std::logic_error("some error message goes here\n");
SpaceWaster next(current->level - 1, current);
// typical exception-using code doesn't need error return values
thrower(&next);
return;
}

int returner(SpaceWaster *current) {
if (current->checkLevel()) return -1;
SpaceWaster next(current->level - 1, current);
// typical exception-free code requires that return values be handled
if (returner(&next) == -1) return -1;
return 0;
}

int main() {
const int repeats = 1001;
int returns = 0;
SpaceWaster first(1000, 0);

for (int i = 0; i < repeats; ++i) {
#ifdef THROW
try {
thrower(&first);
} catch (std::exception &e) {
++returns;
}
#else
returner(&first);
++returns;
#endif
}
#ifdef THROW
std::cout << returns << " exceptions\n";
#else
std::cout << returns << " returns\n";
#endif
}

Mickey Mouse benchmarking results:

$ make throw -B && time ./throw
g++ throw.cpp -o throw
1001 returns

real 0m0.547s
user 0m0.421s
sys 0m0.046s

$ make throw CPPFLAGS=-DTHROW -B && time ./throw
g++ -DTHROW throw.cpp -o throw
1001 exceptions

real 0m2.047s
user 0m1.905s
sys 0m0.030s

So in this case, throwing an exception up 1000 stack levels, rather than returning normally, takes about 1.5ms. That includes entering the try block, which I believe on some systems is free at execution time, on others incurs a cost each time you enter try, and on others only incurs a cost each time you enter the function which contains the try. For a more likely 100 stack levels, I upped the repeats to 10k because everything was 10 times faster. So the exception cost 0.1ms.

For 10 000 stack levels, it was 18.7s vs 4.1s, so about 14ms additional cost for the exception. So for this example we're looking at a pretty consistent overhead of 1.5us per level of stack (where each level is destructing one object).

Obviously C++0x doesn't specify performance for exceptions (or anything else, other than big-O complexity for algorithms and data structures). I don't think it changes exceptions in a way which will seriously impact many implementations, either positively or negatively.

Is noexcept useless when not throwing is zero-cost?

Is the noexcept specifier useless if your implementation has a zero-cost (if nothing is thrown) exception model?

No, noexcept would be useful even if exceptions had no performance impact at all.

One example is that noexcept can signal which algorithm can be used. For example algorithms that use std::move_if_noexcept or many of the standard type_traits could behave differently depending on the presence of noexcept.

This can have a significant impact for common features like std::vector which will be much slower if you elements' move constructor isn't noexcept. std::vector will copy all elements when reallocating instead of moving them if the move constructor is not noexcept to preserve the strong exception guarantee.

Overhead of exception handling in D

I cannot speak about D or any of its compilers, but I can tell you some about C++, Windows, and the Visual Studio compiler. This might help you understand roughly how D does things.

First, exception handling on 32- and 64-bit machines is different. The x86 ABI (prolog/epilog, unwinding, calling convention) is more lax, so the compiler and the program itself must do more work. The x86-64 ABI is stricter and the OS plays a bigger role, making it easier for the program itself to work with exceptions. If you're running D on Windows, then it likely uses SEH (structured exception handling) like C++ does.

Again, all of my answers below relate to Windows, C++, and Visual Studio.

What if I write no exception handling
code?

x86/x86-64: There is no cost for that method.

What if I do, but no exceptions are
ever thrown?

x86: There is a cost even when exceptions aren't thrown. Exception handling information is pushed into the TIB (thread information block) such as the initial scope and the function-specific exception handler. In order to know what objects to destruct and what handlers to search, a scope variable is maintained. This scope variable is updated as you enter try blocks and construct stack objects that have destructors.

x86-64: Because of the stricter rules, there is no extra code (or very very minimal). This is a big advantage over x86.

What if I do, and exception are
thrown?

On either x86 or x86-64, there will definitely be a hit. Exceptions should be exceptional though. Do not use them for regular control flow. Only use them to signal truly exceptional, unexpected events. Essentially, you should never have to worry about the cost of exceptions. Even if they took 2 seconds, you shouldn't care, because they should only happen when everything is going south anyway.

With that said, throwing exceptions on x86-64 is more expensive than throwing them on x86. The x86-64 architecture is optimized around the case of no exceptions being thrown, which, ideally, is nearly all the time.


Big picture:

  • I can't see propagating error codes being materially faster than exception handling, especially on x64 platforms.
  • I doubt you will ever find exception handling being a problem unless you're abusing exceptions.
  • If you're unsure, you should measure the performance of your code.

Throwing exception versus returning a result code

It is a good idea to mimic the behavior of the standard library functions unless one has a specific reason not to. BTW, since tr1, STL has a fixed-length string class built in. Lets see what it does. The only example implementation I have handy is Visual C++ 2010.



std::tr1::array<int,5> arry;
arry[10] = 42; // Oopsie. There is no element 10.

When compiled and run as the "Debug" version, I get an assert failure. When compiled for "Release" the offensive statement quietly does ... NOTHING. It is optimized right out of existence. Okay, maybe that is not always what one would want. Forget what I said about mimicking the STL, or at least Microsoft's implementation. Train of consciousness continues...

I think it is fair to say that if the program tries to set an out of range cell, that is a logic error in the program. In mission-critical software it might be a good idea to have code in place to deal with a situation like that and recover from it, while trying like heck to make sure it can never, never happen.

So the answer is, throw an exception of type std::out_of_range.

So there.

C++ return value versus exception performance

I don't know where you read this, but it is surely incorrect. No hardware designer would make exceptional circumstances, which are by definition uncommon, work FASTER than normal ones. Also keep in mind that C, which according to TIOBE is the most popular systems language, does not even support exceptions. It seems EXTREMELY unlikely that processors are optimized for ONE language's exception handling, whose implementation is not even standardized among compilers.

Even if, somehow, exceptions were faster, you still should not use them outside their intended purpose, lest you confuse every other programmer in the world.

In what ways do C++ exceptions slow down code when there are no exceptions thown?

There is a cost associated with exception handling on some platforms and with some compilers.

Namely, Visual Studio, when building a 32-bit target, will register a handler in every function that has local variables with non-trivial destructor. Basically, it sets up a try/finally handler.

The other technique, employed by gcc and Visual Studio targeting 64-bits, only incurs overhead when an exception is thrown (the technique involves traversing the call stack and table lookup). In cases where exceptions are rarely thrown, this can actually lead to a more efficient code, as error codes don't have to be processed.

Should exceptions ever be caught

Not all exceptions are fatal. They may be unusual and, therefore, "exceptions," but a point higher in the call stack can be implemented to either retry or move on. In this way, exceptions are used to unwind the stack and a nested series of function or method calls to a point in the program which can actually handle the cause of the exception -- even if only to clean up some resources, log an error, and continue on as before.

Why are exceptions so rarely used in C++

Observation bias at work here.

A significant fraction of C++ code is for systems programming and embedded systems. (After all, C++ is just one of many options for applications programming, and many of the alternatives have fancier RAD environments, but in systems work, it's often the highest level language for which a compiler is available, by a wide margin). And most embedded systems as well as a significant chunk of systems development work have constraints that rule out exceptions.

If because of your focus, you tend to search out this sort of code, it's entirely possible that the C++ code you've seen doesn't use exceptions.

That doesn't mean that there isn't C++ code using exceptions -- there is, and a lot of it. It may just not appear in code designed to solve problems that are interesting to you.

Throwing exception vs return code

Throw exceptions when an expectation has not been met, return a status code when you're querying for status.

for example:

/// pops an object from the stack
/// @returns an object of type T
/// @pre there is an object on the stack
/// @exception std::logic_error if precondition not met
T pop();

/// queries how many objects are on the stack
/// @returns a count of objects on the stack
std::size_t object_count() const;

/// Queries the thing for the last transport error
/// @returns the most recent error or an empty error_code
std::error_code last_error() const;

and then there's the asio-style reactor route coupled with executor-based futures:

/// Asynchronously wait for an event to be available on the stack.
/// The handler will be called exactly once.
/// to cancel the wait, call the cancel() method
/// @param handler is the handler to call either on error or when
/// an item is available
/// @note Handler has the call signature void(const error_code&, T)
///
template<class Handler>
auto async_pop(Handler handler);

which could be called like this:

queue.async_pop(asio::use_future).then([](auto& f) {
try {
auto thing = f.get();
// use the thing we just popped
}
catch(const system_error& e) {
// e.code() indicates why the pop failed
}
});


Related Topics



Leave a reply



Submit