Jump to content

  • Log In with Google      Sign In   
  • Create Account

Awesome job so far everyone! Please give us your feedback on how our article efforts are going. We still need more finished articles for our May contest theme: Remake the Classics

are exceptions expensive on a render engine?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
24 replies to this topic

#1 はとぶ   Members   -  Reputation: 255

Like
0Likes
Like

Posted 10 May 2012 - 11:16 AM

ok, basically that... I'm starting to build a render engine in DirectX, but I'm not sure how expensive could be to use exceptions to handle some errors.

I'm thinking on doing something like:

HR = SomeD3DFunction( bla bla bla );

if( FAILED( HR ) )
	 throw SomeDeviceException();


is it safe?.. or would it be too expensive to use?

thanks!
"lots of shoulddas, coulddas, woulddas in the air, thinking about things they shouldda couldda wouldda donne, however all those shoulddas coulddas woulddas ran away when they saw the little did to come"

Sponsor:

#2 SiCrane   Moderators   -  Reputation: 6667

Like
1Likes
Like

Posted 10 May 2012 - 11:33 AM

From a performance perspective, you'd only want to throw an exception on error either if the chances of the error occurring are very low or if program execution is so hosed that performance doesn't matter since you're about to bail (ex: to desktop or the main menu). For MSVC I generally use the estimate of one in ten thousand as the cut off. If the call is more likely to fail than one in ten thousand calls, then don't use an exception. If less, then an exception is fine. So a function like IDirect3DDevice9::TestCooperativeLevel() would be a bad candidate for throwing an exception since a lost device is something that can easily happen and also can be recovered from so you aren't about to bail.

#3 Ameise   Members   -  Reputation: 461

Like
1Likes
Like

Posted 10 May 2012 - 11:52 AM

From a performance perspective, you'd only want to throw an exception on error either if the chances of the error occurring are very low or if program execution is so hosed that performance doesn't matter since you're about to bail (ex: to desktop or the main menu). For MSVC I generally use the estimate of one in ten thousand as the cut off. If the call is more likely to fail than one in ten thousand calls, then don't use an exception. If less, then an exception is fine. So a function like IDirect3DDevice9::TestCooperativeLevel() would be a bad candidate for throwing an exception since a lost device is something that can easily happen and also can be recovered from so you aren't about to bail.


I'm not sure if I entirely agree with that. Exceptions are used in situations where behavior is, well, exceptional. You use exceptions when you _want_ to be able to recover from an error. You use assertions in situations where you don't.

Basically, assertions verify that the contracts between functions are being honored; if a function specifies that it cannot take a NULL parameter, yet a NULL parameter is passed to it, you should use an assertion; that is a logic error. If it was data passing the NULL parameter, the caller should have been aware of the contract and verified it beforehand, possibly using a thrown exception to early-out.

If he's working with code where he must roll-back logic due to a manageable error, then an exception is correct. If it's an unrecoverable error, one should use an assertion.

Ideally, with a renderer, you'd rarely if ever use assertions (only use them to validate function contracts); you want your renderer to be able to recover from errors and at least restart itself.

The alternative to using an exception is to check the return value and do a series of returns, which is no more efficient, and is far harder to read/understand.

#4 SiCrane   Moderators   -  Reputation: 6667

Like
1Likes
Like

Posted 10 May 2012 - 12:26 PM

Assertions are used to flag situations that should never happen, and only arise from the fundamental assumptions of the programmer being violated. This does not mean just any situation that you know can happen but you can't or just don't want to recover from. For example, being unable to open a script file from disk that contains the core logic of your program is not recoverable, but does not warrant an assertion because file system errors, running the program from a network or external drive or plain bad installs are situations that can actually happen. Every assertion that gets tripped should represent a programmer error, and should not get used for any other situation. In other words, if the end user ever sees an assertion get tripped, then a programmer somewhere has screwed up.

The efficiency of using return values compared to exceptions depends on the relative frequency of the failure situation. Throwing and catching an exception is an extremely costly operation, so if an error occurs frequently then, from a performance perspective, exceptions become more costly than using return values. On the other hand, if an error almost never occurs, then it's more efficient to not have to check a return value. The break even point depends on multiple factors including compiler, but on MSVC it usually turns out to be the roughly one in ten thousand value I mentioned.

#5 Ravyne   Members   -  Reputation: 2763

Like
1Likes
Like

Posted 10 May 2012 - 12:26 PM

Be aware that exceptions aren't just something you can add in willy-nilly. Firstly, there's overhead for simply *having* exceptions at all, you don't have to throw an exception to get a performance hit, its there all the time. The overhead is probably negligible in many functions, but for functions which might be called in a tight loop it can be significant.

Another concern is that there's still more overhead, either in implementation, at runtime, or both, to make sure your functions are exception-safe. It's also something that can be difficult to get right. An example is that if you allocate memory inside of a function, and then later the stack is unwound due to an exception, you might not execute the code that frees the memory. If your program is going to bail anyhow, its not big deal, but if you're able to recover from the exception higher up the chain, you've got a source of memory leaks. Proper use of smart pointers can go a long way to solving that particular problem, but its a good example of how using exceptions radically changes the way you have to think about your program's correctness.

On some development platforms portability can also be a concern as well, because exceptions might not be supported. Sticking to PC and (probably) modern consoles it will be well-supported, but you might be out of luck if your code will ever run on an embedded platform or portable consoles. I'm not privy to specifically which development systems might or might not support them, but historically speaking, RTTI and exception handling are usually the first things thrown out the window on less-capable platforms, so its something to keep in mind.

#6 Washu   Senior Moderators   -  Reputation: 3113

Like
4Likes
Like

Posted 10 May 2012 - 12:37 PM

I tend to be in the group that suggests against using exceptions unless you actually know what you're doing.

Exceptions introduce a lot of extra work YOU have to manage (smart pointers being one simple example). They also tend to add a certain amount of performance overhead (in C++, this is not completely true in managed languages). Why? Well, there's a couple of things to realize: When an exception is thrown the stack has to be unrolled. In C++ this means fully constructed objects whose scope ends during the stack walk up to the first exception handler that matches the thrown exception has to be destructed. For things like smart pointers, containers, etc. this can be quite expensive (when a vector goes out of scope, it has to first walk all the existing items in it and call their destructors, then release the memory it had allocated).

In addition, there aren't a whole lot of areas in a RENDERER where you actually NEED exceptions. There are a few things that MIGHT call for them, such as errors like "Device was removed." (I've gotten that one before :P). However, in general most errors are quite recoverable, such as the device being lost. In C++ the general rule is that "exceptions are for exceptional events," and a device being lost is a very common occurrence (in D3D9). Other similar issues, such as running out of GPU memory when trying to allocate a texture or vertex buffer are similarly not that uncommon and should be dealt with in a fairly clean and failproof mechanism. All of this generally suggests that you should either provide error code returns (instead of exceptions) or some other form of error notification.

As a last note: Never, and I do mean it, mix exceptions with return codes. There is no interface more confusing than one that attempts to report errors in more than a single way.

As an additional note on the managed side of things: The reason exceptions are more prevalent, other than just a language design decision is that it is quite a bit cheaper for exceptions to propagate than it is in a language like C++. This is because, in most managed languages (like C#, Java, Python), there is no need to walk up the stack destroying objects whose lifetime has ended. These will be automatically collected by the GC. This simplifies the stack walk and reduces the amount of additional information the compiler has to track to figure out what needs to be cleaned up.

Edited by Washu, 10 May 2012 - 12:40 PM.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.
ScapeCode - Blog | SlimDX


#7 SiCrane   Moderators   -  Reputation: 6667

Like
2Likes
Like

Posted 10 May 2012 - 12:48 PM

Be aware that exceptions aren't just something you can add in willy-nilly. Firstly, there's overhead for simply *having* exceptions at all, you don't have to throw an exception to get a performance hit, its there all the time. The overhead is probably negligible in many functions, but for functions which might be called in a tight loop it can be significant.

To be fair, checking the return value for functions called in a tight loop can have significant overhead too. In any case, degree and kind for exception overhead is an implementation specific deal. Some compilers add prologue and epilogue code to every function, which obviously causes overhead for every function. On the other hand, some other compilers have what are called "zero overhead exceptions", which is a marketing claim rather than an accurate claim, but the cost takes the form of additional data tables added to the executable rather than specific runtime costs like the prologue/epilogue code. It slows down load time due to increased size, but doesn't actually slow down code execution until an exception is actually thrown. Additionally, compilers frequently give mechanisms to help manage exception handling overhead, such as MSVC's __declspec(nothrow).

Edited by SiCrane, 10 May 2012 - 01:29 PM.
grammar


#8 SiCrane   Moderators   -  Reputation: 6667

Like
2Likes
Like

Posted 10 May 2012 - 12:57 PM

They also tend to add a certain amount of performance overhead (in C++, this is not completely true in managed languages). Why? Well, there's a couple of things to realize: When an exception is thrown the stack has to be unrolled. In C++ this means fully constructed objects whose scope ends during the stack walk up to the first exception handler that matches the thrown exception has to be destructed. For things like smart pointers, containers, etc. this can be quite expensive (when a vector goes out of scope, it has to first walk all the existing items in it and call their destructors, then release the memory it had allocated).

That's not really an overhead of exception handling. The compiler still needs to unwind the stack and destroy the same objects no matter what language constructs you use to handle program flow. Use a return to leave a function or use an exception to leave a function, the stack gets unwound either way.

#9 Washu   Senior Moderators   -  Reputation: 3113

Like
1Likes
Like

Posted 10 May 2012 - 01:34 PM


They also tend to add a certain amount of performance overhead (in C++, this is not completely true in managed languages). Why? Well, there's a couple of things to realize: When an exception is thrown the stack has to be unrolled. In C++ this means fully constructed objects whose scope ends during the stack walk up to the first exception handler that matches the thrown exception has to be destructed. For things like smart pointers, containers, etc. this can be quite expensive (when a vector goes out of scope, it has to first walk all the existing items in it and call their destructors, then release the memory it had allocated).

That's not really an overhead of exception handling. The compiler still needs to unwind the stack and destroy the same objects no matter what language constructs you use to handle program flow. Use a return to leave a function or use an exception to leave a function, the stack gets unwound either way.

Yes and no. If the performance were the same then there would be no reason to use error codes over exceptions. There is a performance difference though, and it does directly relate to the tracking of what needs to be destroyed when an exception is thrown, either through additional tracking information on the stack or in static tables. All of which is generated by the compiler and thus invisible to the programmer. Although the table based approach (which uses the current call stack to find which destructors to call, I believe) is supposed to be "cost-free" until an exception is thrown.

My only other thing is that I do tend to prefer exceptions over error codes, mainly due to the reduction in spaghetti code that most return based error handling introduces (which it doesn't have to, but it tends to). But that only really works if you've designed your software solution with exceptions in mind.

Edited by Washu, 10 May 2012 - 01:41 PM.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.
ScapeCode - Blog | SlimDX


#10 e‍dd   Members   -  Reputation: 2042

Like
1Likes
Like

Posted 10 May 2012 - 01:47 PM

Depends on the exception handling mechanism. The old SJLJ "personality" that GCC used to use by default was horribly slow. The newer DWARF "personality" will often be cheaper than checking error codes, as the cost is paid only when exceptions are thrown (which should be rare -- you certainly don't want to replace error codes with exceptions entirely). The Visual C++ exception handling mechanism is somewhere in the middle, as far as I understand it; quite fast but not free.

But I'd say that any such decision should hang far more on the skill and experience of the people writing and maintaining the code than on any concerns about efficiency. Writing exception-safe code requires a meticulous attention to detail and an awareness of which code can/may throw. Example:

struct Contact // for an address book app, or something.
{
    Contact(const std::string &name, const std::string &address) :
        name(name),
        address(address)
    { }

    std::string name;
    std::string address;
};

The compiler-generated copy-assignment operator isn't strongly exception safe, as if address assignment throws, you'll end up with a corrupted Contact (in the sense of a name/address mismatch). A contrived example, but it shows that even if RAII is applied ruthlessly, you aren't out the woods; you have to be ever vigilant.

The burden is probably greater than checking the return code of every function. But with that said, C++'s built-in handling of stack-unwinding and deterministic destruction arguably provides a better baseline than most any other mainstream languages for handling exceptions (when treated as non-fatal conditions).

#11 SiCrane   Moderators   -  Reputation: 6667

Like
3Likes
Like

Posted 10 May 2012 - 01:48 PM

Yes and no. If the performance were the same then there would be no reason to use error codes over exceptions. There is a performance difference though, and it does directly relate to the tracking of what needs to be destroyed when an exception is thrown, either through additional tracking information on the stack or in static tables.

I'm not saying that there's no performance difference, I'm saying that objects need to be destroyed either way, so the reason you gave isn't one of the causes of performance difference. A vector on the stack gets destroyed whether you leave the function through a return or through an exception, so talking about how expensive it is to destroy a vector and destroy each of its elements is extremely misleading.

#12 SiCrane   Moderators   -  Reputation: 6667

Like
1Likes
Like

Posted 10 May 2012 - 04:04 PM

The compiler-generated copy-assignment operator isn't strongly exception safe, as if address assignment throws, you'll end up with a corrupted Contact (in the sense of a name/address mismatch). A contrived example, but it shows that even if RAII is applied ruthlessly, you aren't out the woods; you have to be ever vigilant.

Is this really a problem with exception handling? If you disable exception handling will that prevent a corrupt object? If instead of an assignment operator that uses exceptions to signal errors, the compiler created and used functions that returned error/success would that prevent corruption? The problem is the underlying algorithm: memberwise assignment. This is completely orthogonal to what mechanism is used to signal errors. If the underlying algorithm is bad, whatever error signaling method you use won't help.

#13 e‍dd   Members   -  Reputation: 2042

Like
1Likes
Like

Posted 10 May 2012 - 04:39 PM

Is this really a problem with exception handling?

If your string copying functions return error codes, you'll be more aware of the problem. Of course, the problem exists either way (and arguably copy-and-swap is a nicer solution than error codes) but it's easy to assume (I feel) that exception safety 'composes' in this way, when in fact it does not.

If instead of an assignment operator that uses exceptions to signal errors, the compiler created and used functions that returned error/success would that prevent corruption?

No, but I very much doubt that would have been standardised if exceptions didn't exist, because a compiler can't deduce whether a particular return type/code indicates an error or a 'proper' result. So without exceptions, the programmer would be forced to write the assignment code, providing the opportunity to spot the problem. Of course, without exceptions you would have a hard time implementing "Contact &operator= (const Contact &rhs)" with that signature in the first place, as there's no place to return an error.

If the underlying algorithm is bad, whatever error signaling method you use won't help.


The algorithm is sound in the abstract, but it's easier to implement a concrete version of it correctly when exceptions aren't involved, as you're forced to confront the error conditions.

Of course, it takes discipline to make sure such error codes are checked everywhere, but I often wonder whether it requires more or less discipline to use exceptions correctly. It's easy for the novice to be lulled in to thinking things are easier because they don't have to check error codes everywhere. They'd be right, but other adjustments must be made which arguably require a deeper understanding of the surrounding code an context.

FWIW, I'll likely continue to use exceptions in my personal projects, but I have to take care when collaborating (e.g. at work) because of the different mindset required.

#14 SiCrane   Moderators   -  Reputation: 6667

Like
0Likes
Like

Posted 10 May 2012 - 04:51 PM

The algorithm is sound in the abstract, but it's easier to implement a concrete version of it correctly when exceptions aren't involved, as you're forced to confront the error conditions.

No, it's not. If you copy the first member and then the copy for the second member fails, you're left with a corrupt object. It doesn't matter how the error is signaled, you've changed the state for the first member already. Implementing a correct version requires a change in algorithm.

#15 e‍dd   Members   -  Reputation: 2042

Like
0Likes
Like

Posted 10 May 2012 - 05:04 PM


The algorithm is sound in the abstract

No, it's not.


We're talking past each other here. What I mean by "sound in the abstract" is that assignment is a perfectly reasonable thing to be able to want to do.

In either case, the pseudocode must be:

string newname;
string newaddress;

if (newname = rhs.name fails)
    return_with_failure_indication();

if (newaddress = rhs.address fails)
    return_with_failure_indication();

// Can't fail now, just exchanging a few pointers etc
swap_internals(newname, this->name);
swap_internals(newaddress, this->address);

I'm saying that I believe it's easier to see that this is the correct way of doing things when you choose or are forced to use error codes over exceptions.

#16 Antheus   Members   -  Reputation: 2369

Like
0Likes
Like

Posted 10 May 2012 - 05:10 PM

as if address assignment throws, you'll end up with a corrupted Contact (in the sense of a name/address mismatch).


I'm probably missing something, but if constructor throws, you don't have an object.

Or,
Contact c(name, address);
// I can only get here if no exception was thrown


#17 e‍dd   Members   -  Reputation: 2042

Like
0Likes
Like

Posted 10 May 2012 - 05:12 PM

as if address assignment throws, you'll end up with a corrupted Contact (in the sense of a name/address mismatch).


I'm probably missing something, but if constructor throws, you don't have an object.

Or,
Contact c(name, address);
// I can only get here if no exception was thrown


I agree with everything you've said Posted Image

I'm not sure what you're contesting? Note I'm talking about copy assignment, not copy construction.

Edited by edd², 10 May 2012 - 05:13 PM.


#18 Krohm   GDNet+   -  Reputation: 1751

Like
0Likes
Like

Posted 11 May 2012 - 02:02 AM

ok, basically that... I'm starting to build a render engine in DirectX, but I'm not sure how expensive could be to use exceptions to handle some errors.

I'm thinking on doing something like:

HR = SomeD3DFunction( bla bla bla );

if( FAILED( HR ) )
	 throw SomeDeviceException();


is it safe?.. or would it be too expensive to use?

thanks!

There's no such thing as SomeD3DFunction.
Places where I use exceptions:
- Compiling shaders
- Loading/creating graphics resources

Those paths are still exception free, and will probably be for a long time
- Buffer map
- DrawCalls

It's worth noticing most D3D calls don't fail because of "some reason". Most fail because of static reasons such as incorrect parameters or wrong internal state.

#19 mdwh   Members   -  Reputation: 520

Like
0Likes
Like

Posted 11 May 2012 - 07:59 AM

Just a few thoughts:

* One of the advantages of exceptions is you can report a failure in a constructor, which can't be done with return codes. You can get round it by setting a "success" flag which is checked, or having a light constructor and doing everything in an init() function, but both seem less desirable to me.

* The STL works with exceptions anyway, so surely anyone using it with C++ ought to be aware of how exceptions work anyway?

* It's true that exceptions create complications with freeing memory, as the only thing guaranteed to be called is the destructors of objects on the stack, so you end up needing smart pointers and so on. The flip side is that, whilst doing things the C-style way with return codes and manually deleting memory is easier (at least for me) to get your head round, it is also easy to lead to its own problems of memory leaks and missed error returns. So I'd argue using smart pointers is a good thing anyway, and shouldn't be a reason against exceptions.

* I wonder if part of the problem is that DirectX just returns everything as a "failure", even for things like losing the device which are perfectly normally? I mean, if MS had sorted things so that "failure" was only for the exception cases, then using exceptions always would make more sense. But instead, it's left to the caller to try to decide which category each "failure" falls into.

Edited by mdwh, 11 May 2012 - 08:00 AM.

https://freecode.com...jects/conquests - Conquests, Open Source Civ-like Game for Windows/Linux
https://freecode.com...cts/gigalomania - Gigalomania, Open Source RTS for Windows/Linux/OS X/Symbian/Android/Maemo/Meego

#20 AgentC   Members   -  Reputation: 678

Like
0Likes
Like

Posted 11 May 2012 - 03:35 PM

I would second the idea to be wary of exceptions in a rendering engine, though not so much because of performance, but because of usability and robustness. While rendering may require complex dependencies between objects (for example a material referring to textures, shaders and constant buffers) the easy way to bail out of most error situations, such as a missing/failed texture, is simply to not render, or to replace the missing object with an "error asset" to indicate the error visually, while also logging an error and returning an error code.

If you go with exceptions, there's the question of whether you ever catch exceptions in the renderer itself, or whether you always let them propagate to the user code. If you let them propagate, throwing an exception in the middle of the render loop (let's say that object 90 out of 100 threw an exception when rendering, and continues to do so on subsequent frames) may leave out calls to important Direct3D functions, like EndScene() or Present(), which leads to the application appearing frozen even if the user code catches the exception.

"If I die I have to go before him, and he will ask me 'Forward or deferred rendering?' And if I don't know which he will cast me out of Valhalla and laugh at me! That's Crom - strong in his mountain!"





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS