C++ lightweight exception

Started by
20 comments, last by Zahlman 17 years ago
Quote:Original post by Bregma
Quote:Original post by Xai
both overheads can be significant. In fact there are 3:

1. The overhead the compiler must introduce to support the possiblity of an exception being thrown, this is paid pretty much throughout any project which has exception support not disabled, except in tight circumstances that the compiler has optimized with complete knowledge that no exception actually can occur.

Which, in any major C++ runtime published in the last half decade or so, is zero. None. Zilch. Nada. Not a sausage. Well, maybe an extra instruction or two when entering an explicit try-block, but nothing worse than the overhead of a function call. If you do the smart thig and design your software to minimize try-blocks, you will not be able to measure this Cost.


I've been wondering about this. Isn't "an extra instruction or two when entering an explicit try-block" absolutely required? Otherwise, how does the stack-unwinding procedure know when to stop?

For that matter, just how does it work, anyway?
Advertisement
Quote:Original post by SunTzu
http://gamearchitect.net/Articles/ExceptionsAndErrorCodes.html would disagree.


It mainly disagrees when the error status is sent up only one function. I would be curious to see how propagating an error up five function calls (an average situation) measures between one exception and five error return codes.

Zahlman: link.
Indeed; the main point was, the claim that there is zero cost and zilch overhead for exceptions is completely untrue.

I strongly suspect that regardless of whether it is one level, five, or more, the actual penalty for handling exceptions with error codes or exceptions will be similar. However, the point is that turning on exception handling automatically incurs a cost in just about every function in the program (not "maybe one or two extra instructions in an explicit try block"), so the situation is basically: either don't use exceptions at all and disable them completely, or enable them in which case you might as well use them as you're paying for them whether you use them or not.
Quote:Original post by Zahlman
I've been wondering about this. Isn't "an extra instruction or two when entering an explicit try-block" absolutely required? Otherwise, how does the stack-unwinding procedure know when to stop?

Yes. But like I said, it's about the same amount of overhead as when a function is entered. The overhead is necessary to register the catch clauses. It's sort of like pushing arguments on the stack.

Other than try-blocks, (and of course throwing and catching), the use of exceptions add absolutely no extra overhead to an application.
Quote:
For that matter, just how does it work, anyway?

I can't speak for for the Microsoft compiler, but I can describe how GCC unwinds its stack.

There are two phases. The first phase crawls back through the stack looking for a registered catch that will receive the thrown object (the RTTI system is used internally for this). If nothing is found, terminate() is called, which by default calls abort().

If a catch clause is found, the stack is unwound, one frame at a time, executing any automatic destructors as necessary, until the appropriate catch-block is reached

It's the stack crawl and RTTI that gives exception handling the most overhead, and that's all done during the throw.

--smw

Stephen M. Webb
Professional Free Software Developer

Quote:Original post by Bregma
Other than try-blocks, (and of course throwing and catching), the use of exceptions add absolutely no extra overhead to an application.


Quote:http://gamearchitect.net/Articles/ExceptionsAndErrorCodes.html
Neither Microsoft C++ nor GCC implement zero-overhead exception handling. Instead, both compilers add prologue and epilogue instructions to track information about the currently executing scope. This enables faster stack unwinding at the cost of greater runtime overhead when an exception isn't thrown.
...
In Microsoft C++, the overhead of exception handling in the absence of an exception is the cost of registering an exception handler as each function is entered and unregistering it as the function is exited. As far as I can tell, that's three extra push instructions and two movs in the prologue, and three more movs in the epilogue.


Sigh.
Quote:Original post by SunTzu
Sigh.


I won't try to speak for Bregma, but I believe the relevant metric here is extra overhead, and your article only mentions total overhead. To evaluate the extra overhead of exception handling, you consider the total overhead of exception handling and substract the total overhead of handling errors through return codes. Because, well, we're comparing two methods here, not comparing exception handling to the absence of any handling at all (where we already know that exception handling is slower).

So, let's consider a typical example:

// Exception handling method: // We call the function and store the result. If an exception is triggered, // we let it propagate up the call tree.int number = computation();// Return code method:// We call the function, check that the return code is correct. If it isn't, we// propagate up the call tree.int number;if (computation(number)){  return ERROR;}


I have voluntarily removed the cleanup code from both examples, since it will be fairly similar. I have also considered "number" to be an integer. In the case of a class, it would have been trickier (a design problem if the class has no default constructor) and would have caused optimization problems in the second case because of the redundant initialization. Note that I've also considered the average case where an exception will propagate up the call stack through several functions before reaching the corresponding catch: in an average program, the number of try blocks is much smaller than the number of function calls, so the per-function overhead of try block breadcrumb laying is negligible.

So, the first call has three push and five mov in addition to the standard function call, which is the expected overhead for exceptions. On the second call, we have at least one compare, one jump and one additional argument. So, how do the two compare?

A possible counter-argument is the fact that error handling does not span over the entire project. It error handling only occurs in a small part of your project, then it is clear that said portion is cleanly separated from the rest of the project. You may then gain from delegating that project portion to a library compiled with exceptions enabled, and the rest of the project to one or more libraries compiled with exceptions disabled, both on the performance aspect, and on the design aspect.
Something to note:
Considering the try setup cost is all fine, but we are talking about catching exceptions here.

If performance is going to be an issue maybe you should look at your code more closely, i'd suggest that having a number of try-catch blocks to the point that it becomes a performance concern would indicate a bad design.

Similarly, there should be no need to worry about the cost of throw-ing an exception. Something should have gone horribly wrong, else you would have been checking on a return type or error flag not hitting the panic button.

Just my 2 cents, try-throw-catch is just a glorified goto after all.
Since I started what threatens to become a holy war, here's my take on it.

Exceptions need to introduce some overhead. In some cases, this may be critical.

But my original question refered to "real-world" example, namely the use of it in persistence library, where exception will never be thrown.

In that case, building and linking the entire library (some template 15 classes supporting 30 to some 80 types in final version) either with exceptions completely disabled, or proper exceptions, resulted at worst in 4% overhead, but depending on what inlining produces, usually with 0 overhead.

While there may be some overhead on paper, the simple cost of serializing STL collections of strings and other objects dwarfs any overhead caused by exceptions.

I feel that this is important because of premature optimization falacy. Exceptions in my case make code elegant. There isn't a single command other than << or >> in any of the persistable classes, and the only exception handler is around the network or file code that reads raw data.

And this was my main concern. Is it worth throwing away simple, reliable design that completely hides implementation at the expense some performance penalty. The answer is a big NO.

As always, knowing the details about various platforms may help, but premature optimization in this case would probably cause quite a few headaches and perhaps even a few timed bombs.
Original post by SunTzu
Quote:http://gamearchitect.net/Articles/ExceptionsAndErrorCodes.html
Neither Microsoft C++ nor GCC implement zero-overhead exception handling. Instead, both compilers add prologue and epilogue instructions to track information about the currently executing scope. This enables faster stack unwinding at the cost of greater runtime overhead when an exception isn't thrown.


Consider this (the Gypsy lays her cards out on the table...)

If a C++ compiler were required to emit prologue and epilogue code for each and every stack frame it emits, how would you you ever hope to link to the vast majority of third party libraries and code base written in C? You couldn't. The function prologue emitted for a C function, a C++ function with exceptions disabled, and a C++ function with exceptions enabled are identical.

The compiler designers at the major vendors realized years ago that reducing the runtime overhead of a throw at the expense of slower overall response was a bad tradeoff. They responded to the complaints, and there were many. Runtime overhead due to enabled exceptions is a thing from a previous century.

Throwing and catching exceptions is expensive. It should be done only under exceptional circumstances.

--smw

Stephen M. Webb
Professional Free Software Developer

Quote:Original post by Bregma
If a C++ compiler were required to emit prologue and epilogue code for each and every stack frame it emits, how would you you ever hope to link to the vast majority of third party libraries and code base written in C? You couldn't.


Actually, you could.

For instance, the prologue and epilogue could be made part of the function itself, without affecting that function's linkage — from the outside, it would only appear as if the function was 'playing' with the stack upon call and return, which is a pretty innocent and indifferent thing for a function to do.

Then, the exception is thrown, reaches the nearest compatible catch breadcrumb on the stack, and unwinds everything using the jump information that was placed on the stack by the prologue (and wasn't removed by the epilogue).

To be honest, I cannot seem to find a way to perform stack unwinding correctly without a prologue and epilogue adding some information to the stack in addition to the usual stack frame data.

This topic is closed to new replies.

Advertisement