Exceptions and performance

Started by
30 comments, last by MaulingMonkey 19 years, 4 months ago
Quote:Original post by Koshmaar
better to say that I prefer to check for null instead :-)


really, so you would rather have branching code via an if-statement that executes every time it reaches that statement rather than having no normal branching code at all and only handle the exceptional case when it actually does happen, i know which i prefer [smile]

Quote:Original post by Auron
Note that dynamic memory is not deallocated when an exception is thrown unless you explicitly do so. This means that you may have to implement handlers at multiple levels whose only purpose is to clean up then throw the exception up to the next level of the call stack.


The problem is you can't always release resources if its not seen in the scope of the catch statement, this is where you need to employ RAII techniques.

Exception Handling in C++ is a little flawed, it was introduced late into the language and wasn't taken very far (as Bjarne's describes in the design of c++), languages such as java do exception handling better. Thats not to say don't use it or its unusable its just to say things like a finally statement would help alot, its still better than doing needless error codes that make you write endless amount of if statements that is completely unneccessarily executated for about 99% of the time (prime examples win32 lib, dx).

Excetion Handling is good for library writers & library users as library users know what should happen in a particular situation in are apps not the library.
Advertisement
People worry way too much about the performance concerns of exceptions and not enough about the difficulty in writing exception-safe code. Typically the exception handler prolog to a function is pretty tiny. If you are curious as to what the actual cost is during nonexceptional situations then you should look at the generated assembly code and decide for yourself.

Under no circumstances can I see proper exception handling slowing down or speeding up an actual game to a measurable degree.
--God has paid us the intolerable compliment of loving us, in the deepest, most tragic, most inexorable sense.- C.S. Lewis
The performance hit of exceptions is not always negligible (though it usually is), even if it is outside loops. And the impact on performance is there even if no exceptions are ever thrown in your program. Just enabling exceptions has some performance impact even if you have no try-catch statements in your program.

The reason for this is that when an exception is thrown, the stack must be unwound. Here's a scenario. Let's say you have a try block, and inside that block you call a function. It calls another function, which calls another, which calls another. Your call stack (starting at the function with the try block) is now 5 functions deep. If an exception is thrown from the deepest function, the program execution must jump up 4 levels in the call stack (well, 4.5 levels because it also has to pop any variables declared inside the try block). All parameters and local variables must be popped off the stack, and the destructors MUST be called for any of those objects that have destructors.

How does this impact performance when no exceptions are thrown? I'm not aware of the intimate details, but at the very least the compiler must add code to the program to manage its own "stack" of destructors that need to be called in case an exception is thrown. Let's assume that the inner function has a for loop in it with a local variable inside the loop's scope. If that local variable has a destructor, the exception stack will have to be manipulated twice for every iteration of that loop.

Let's assume now that each function is contained within its own try block. Each try block may catch different types of exceptions, so I believe each try block needs its own unwind stack. In this case, each variable in the for loop with a destructor causes 10 stack manipulations (2 for each try block). In a complex program that uses exceptions heavily this can add up, even if no exceptions are ever thrown.

The moral of this story is that exceptions should not generally be used to replace simple success/failure return codes, and they should never be used to implement branching within a single function. They should be used for more serious errors (often errors that are difficult to check for or recover from, or errors you expect to almost never occur), and they should be used sparingly. In other words, don't do something like this:

int func(do)
{
try
{
if(!do_operation1())
throw new MyException("Operation 1 failed", ERROR1);
if(!do_operation2())
throw new MyException("Operation 2 failed", ERROR2);
if(!do_operation3())
throw new MyException("Operation 3 failed", ERROR3);
return SUCCESS;
}
catch(MyException *e)
{
CleanUpSomethingThatNeedsToBeCleanedUp();
LogError(e.GetErrorMessage());
return e.GetErrorCode();
}
}

If you're going to do this, you might as well just use a goto because it accomplishes the same thing and it's faster (the syntax is even cleaner). Sometimes it's ok to use exceptions to clean up error handling logic, but you wouldn't throw and catch in the same function. The inner functions would throw exceptions and the outer functions (sometimes several levels up the call stack) would catch them. It would look more like this:

int func(do)
{
try
{
do_operation1();
do_operation2();
do_operation3();

// You could even add something like this
// This is acceptable here because the try-catch block is already there,
// and it doesn't make sense to duplicate the error-handling code
if(something != something_it_should)
throw new MyException("something didn't come out right");
return SUCCESS;
}
catch(MyException *e)
{
CleanUpSomethingThatNeedsToBeCleanedUp();
LogError(e.GetErrorMessage());
return e.GetErrorCode();
}
}

The gain in code readability isn't significant for a simple function like this, but it can be very significant in a complex function, especially when calling a bunch of COM methods (i.e. very useful when using DirectX). It doesn't matter where you put throw statements because they don't have an impact on performance until they're executed. So you can have them in even your tightest loops. But watch out for how many nested try blocks you have, where your try blocks are, and how many objects with destructors you have that get pushed onto to the stack.
Quote:Original post by snk_kid
Quote:Original post by Koshmaar
better to say that I prefer to check for null instead :-)


really, so you would rather have branching code via an if-statement that executes every time it reaches that statement rather than having no normal branching code at all and only handle the exceptional case when it actually does happen, i know which i prefer [smile]



Well, you're right but now in my engine code I almost succesfully avoid the need to allocate memory when game's main loop is running - I do it in the startup etc. so in my case, I really don't care what performance loss gives single if statement :->

Hovewer, as somebody earlier stated, every time you enter try block, compiler must do something to allow proper handling exception when it is actually thrown - I don't really know, but that may be as "slow" as using if and - what's more important - I really don't care, microoptimization is the root of all evil etc. ;-)
This article gives some insight into the code generated by most win32 compilers.
On most other systems the information needed can (supposedly) be extracted from the call stack, but things need to be managed manually on win32 due to the lack of a standardised calling convention.
Quote:Original post by snk_kid


The problem is you can't always release resources if its not seen in the scope of the catch statement, this is where you need to employ RAII techniques.

Exception Handling in C++ is a little flawed, it was introduced late into the language and wasn't taken very far (as Bjarne's describes in the design of c++), languages such as java do exception handling better.


I actually think that Java's exception handling is worse. Finally clause is brittle and so are required exception specifications.
Quote:Original post by doynax
This article gives some insight into the code generated by most win32 compilers.

Some, but not enough. On Windows, C++ exception handling is built on top of SEH. For the nitty gritties of SEH, see A Crash Course on the Depths of Win32 Structured Exception Handling. The basic mechanism described there applies somewhat as well to other OS's running on X86 based cpus - namely using fs to store the chain of exception handlers.

As to the performance impact of exception handling when no exception is thrown, an exception frame is built on the stack - that's about 4 pushes - and a couple of jumps are added that wouldn't be there otherwise. So, as others have said, it's probably not a good idea for time critical code, but for other code it won't have much of an impact.

Here's another excellent resource: The Exception Model.
"I thought what I'd do was, I'd pretend I was one of those deaf-mutes." - the Laughing Man
Quote:
So, as others have said, it's probably not a good idea for time critical code,


Everybody keeps saying that but.. if you have a global try/catch in your WinMain, then all your program is contained in the function calls hierarchy. How can you disable exceptions for the "time critical" code only ?

Y.
Quote:Original post by Ysaneya
What i'm interested in, though, is knowing what performance impact exceptions have on the "normal" code, ie. the code executed 99.999% of the time.

For most code it won't make a difference.

Quote:
For example, if i do a single try/catch in my "main" function, is the performance of all the program's sub functions affected or not? Especially if some functions are called millions of times.

The perform hit is only when entering and exiting the try block, calling a function within a try block stays in that try block - i.e. no overhead.

Quote:
I'd also like to know if a function, that is called a lot, throws an exception (ex.: an inlined function in a maths class, checking for an argument validity), how much is performance affected?
Y.


This the catastrophic case, the performance hit will be large - >100%. The magic words were 'called a lot' and 'inlined', once you have a try-catch block inlining doesn't help nearly as much because you still jump to the try-catch-frame setup and teardown code everytime the function is invoked.
By 'a lot' I assume more than several thousands times per second.

At my last job, I pulled up the VM code, and by just moving the location of a couple of try-catch blocks I doubled the speed of executor (there were 2 try-blocks in the token executor!).

So the general advise is don't worry about the overhead, but when you are working on something that is hit 200,000/sec it matters.


Quote:
Everybody keeps saying that but.. if you have a global try/catch in your WinMain, then all your program is contained in the function calls hierarchy. How can you disable exceptions for the "time critical" code only ?


You can either not put exception handling code in the TC part (unlike the original author of our VM I just mentioned), or you can disable exceptions in the project settings (nothing that use exceptions will compile then). This is typically what you do (along with disabling RTTI) for embedded C++ code.
- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Quote:Original post by Ysaneya
Quote:
So, as others have said, it's probably not a good idea for time critical code,


Everybody keeps saying that but.. if you have a global try/catch in your WinMain, then all your program is contained in the function calls hierarchy. How can you disable exceptions for the "time critical" code only ?

Y.


I think what people are saying is put the exception code-block around a looping code-block, not inside it - particularly when the loop is computationally intensive

aim to knock out entire loops for speed-gain, not the odd comparison or re-assignment (or exception). Use simple, strong programming structures like exceptions wherever you sensibly can and lose them when a piece of code is used so intensively that its necassary.

an exception or other small overhead elements don't affect the speed of an application significantly - they are constant time operations. The things that really impact if you want to optimise are loops. Taking out a loop reduces an order of n+1 complexity to n. Which will have a very noticeable effect on the speed of your code.

This topic is closed to new replies.

Advertisement