Jump to content
  • Advertisement
Sign in to follow this  
Antheus

C++ lightweight exception

This topic is 4107 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I have a persistence library, which boils down to one memory access method:
void read( unsigned char *dst, size_t length)
{
  if (buffer + length > buffer_end) {
    throw // something
  }
  // copy into destination
  memcpy(dst, buffer, length);
}
Given modern compilers (VS 2005, recent gcc), is there an exception that results in minimal overhead? Or are exceptions even at best too heavy weight and it's worth taking the plunge and following the "fail" flag mechanism of streams? In this case the performance benefits would need to severly outweigh the change in API. I'm not interested into any extended information whatsoever, or the location of where the overrun happens. It's a bool state, entire archive is either fully reconstructed, or has corrupted state.

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by Antheus
Or are exceptions even at best too heavy weight and it's worth taking the plunge and following the "fail" flag mechanism of streams? In this case the performance benefits would need to severly outweigh the change in API.


I'd say doubtful that you'd see a severe difference in performance, but all you could really do is put together a couple of stress tests using the two approaches and profile.

It is not just a question of performance in isolation though. If you take the fail-flag approach (and I'd guess that the iostream library works this way because it was designed before exceptions were fully integrated into the standard, given that the remainder of the standard library uses exceptions quite extensively) then performance will also depend on how often the routine is called, how often the fail flag needs to be tested and what the action taken in the event of failure was. That is notwithstanding the development time issues of either you or users of your library forgetting to test the fail flag, of course.

Stroustrup always argued that the penalties incurred by a decent implementation of exception handling would be outweighed by the removal of need for large amounts of "normal" error handling code. I guess whether that is the case or not depends as much on the application as the implementation of exceptions by the compiler.

Share this post


Link to post
Share on other sites
Actually, sorry about this, should have tested the obvious case first.

By using "throw ;" , or "throw true", and not deriving from standard exceptions, the overhead of exception is 0, or the tests run exactly as fast as when exceptions disabled from the build, as well as no throw clauses are used at all.

And by using the exception classes with full error message, the performance hit is 4%.

So never mind this, there is no noteworthy overhead. Even if running without any error checks whatsoever, the maximum performance hit is negligible.

Share this post


Link to post
Share on other sites
If performance is critical, it may be worth testing the implications of something like:


class MyLibMemoryException
{
public:
MyLibMemoryException(){ } // inline and empty constructor
};


and throwing that rather than throwing a built in type like a bool. I'd assume that the difference, if any, would be negligable and it at least provides some information to the catcher about the nature of the exception.

I'd assume that the overhead from throwing the exception you have tried, including a message, would come from the chain of constructors called when creating the exception object.

Share this post


Link to post
Share on other sites
The overhead caused by exceptions is not in instantiating the class (so you won't save much by using a bool instead of std::exception, for example).
The problem is in unwinding the stack once the exception is thrown, and that is exactly the same operation no matter which type you throw.

(This overhead is usually no big deal in any case, but I thought it was worth pointing out that the overhead you do have is not related to which type of exception you throw)

Share this post


Link to post
Share on other sites
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.

2. The cost of creating / initializing an exception prior to throwing it.

3. The cost of unwinding the stack, and comparing exception specifiers at run-time for catching it.

Cost 2, allocating an object is extremely low by comparison to 1 and 3 EXCEPT in situations that are highly mutli-threaded and yet use standard shared new operator (which of course must be thread-safe and do locking or some such). Once you go heavily multi-threaded, you learn to see memory allocation and deallocation as a potentially significant performance cost (still use profiling to determine relevance to you). In normal programs with few threads, the cost doesn't matter at all.

Cost 3 is the big cost, and as mentioned it is the same when you throw a bool, an int, a std::exception or MySuperHugeDoEverythingException.

Share this post


Link to post
Share on other sites
I would have assumed what Spoonbender and Xai are saying about the type of the exception not affecting the perfomance, but for the fact that Antheus's profiling data appears to contradict that.

Antheus - how exactly were you profiling to get this 0 difference on a bool and 4% difference on a std::exception? Perhaps the profiling method was flawed in some way.

Share this post


Link to post
Share on other sites
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.
Quote:

2. The cost of creating / initializing an exception prior to throwing it.

Yes, this may incur a Cost. The exception object must be constructed, and then in all likelihood copied. A simple object (like a bool or a class my_exception { }; will provide pretty close to zero construction or copy cost. A class like std::runtime_error, which constructs a std::string and copies it (likely invoking ::operator new twice) can have a non-trivial cost, but more than likely to disappear into te background noise.
Quote:

3. The cost of unwinding the stack, and comparing exception specifiers at run-time for catching it.

This is the big expense. It's very expensive. This pays for Cost 1 being zero by following the pay-only-for-what-you-use principle that C++ was designed around. The same principle is used to move all of the constant overhead of C-style return value checking into this phase.

This Cost is what leads to the general guideline of using exceptions only for exceptional things. You pay only for what you use, but you pay a premium for that convenience.

--smw

Share this post


Link to post
Share on other sites
Quote:
Original post by EasilyConfused
I would have assumed what Spoonbender and Xai are saying about the type of the exception not affecting the perfomance, but for the fact that Antheus's profiling data appears to contradict that.

Antheus - how exactly were you profiling to get this 0 difference on a bool and 4% difference on a std::exception? Perhaps the profiling method was flawed in some way.


The code is modelled after boost serialization API (that one is unsuitable for me), all parameters to functions are templated, and most of the library gets inlined by the compiler. As long as no STL containers are used, the code generated is almost identical to copying memory blocks directly.

Since dozens of classes (and 30+ template generated versions) get linked into this mess, I originally suspected that exception throwing might cause invisible performance hits, such as some weird jumps, or suboptimal branching. This is not the case.

The reason why there's a difference is due to compiler inlining. In my case, given this is test code, the penalty is irrelevant. In that particular case, the compiler simply chose to inline exception creation several times, causing slightly larger code.

But even then, the difference is so small, and in some builds I've made now none, that I don't consider this to be relevant.

As I said, I should have tested the ideal case first.

In my case, this exception shouldn't be thrown. Since lengths are checked at higher level, only maliciously generated data could possibly get here. This is why my primary concern is that during normal operation, the exception mechanism stays as much out of the way as possible.

Share this post


Link to post
Share on other sites
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.


Oh really?

http://gamearchitect.net/Articles/ExceptionsAndErrorCodes.html would disagree.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!