Exceptions: Bad error handling? (Was: What garbage collection...)

Started by
22 comments, last by MaulingMonkey 18 years, 11 months ago
hehe, that's quite a post :) Sadly I do not have the time to continue at such a pace; therefore, only sparse replies:

Quote:
Quote:See how this is ballooning?

Ehm, no, I don't see

yes, you do: "This means you have a one-liner somewhere that you're going to have to remember exists". What I am pointing out is that (in response to my poking) you keep having to add more functionality/code. Here a RAII class, there a new method, then the report-error-from-dtor monstrosity - tell me with a straight face that it's all conceptually more simple than "if(err != 0) return err;".
I give you a bit of wisdom from Dijkstra: "The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague."

Quote:I've implemented exceptions in a toy compiler. I hope this qualifies me for the glorious title of knowing one of the many ways that exceptions can be implemented :-).

Ah, cool. But there's more to the story: look into SEH, which is the backend for VC's mechanism. Then you will know why throwing is so insanely slow.

Quote:And then you ask what the name or size of the object is, right? No? Refering to something that has yet to exist is one thing. Trying to use something that has yet to exist is another.

I think you misunderstood. In our RTS game, scenarios often refer to art files that don't exist - possibly because someone forgot to commit all files. Since we're distributed over many time zones, waiting for that problem to be fixed is not an option; therefore, things must not explode when a file is missing.

Quote:By this calculation, my computer can find the sizes of over 16 thousand files in 1 second - or in other words, it takes 16-thousandths of a second to find the size of one file. I hardly call this slow :-).

It may actually be less than that (not sure about your benchmark there), but here's the catch: our game has a VFS that needs to enumerate all data files at startup. It organizes them all in a tree, so that file open performance remains fast even if several patch archives and mods have been installed. Windows explorer tells me there are 18181 files in the binaries/ directory, so 16ms for each one would indeed end up being dog slow.

Quote:I feel exactly the same with my comments, and hold the same sentiments towards your APIs, I assure you ^_^.

Ah. This is why I have refrained from touching on exception-related stuff just now - there's no point ;p

I give you one parting thought, though: imagine you have a exception-infested library. If for some reason exceptions must not reach the caller(*), you are forced to wrap every call site in catch(...) (+). Inconvenient, sloow, and you stomp on unexpected exceptions (access violation due to hardware or OS error). Nice!
Now if the library had been written with error codes (which may or may not be more work, depending on the taste of the programmer), it would be much easier to use. If you insist on getting exceptions when something goes wrong, you can write simple and more efficient if(err == WHATEVER) throw whatever(); wrappers.

* excellent example: when passing across language boundaries.
+ non-sequitur: what do your exception specifications make of the fact that anything can "throw" SEH exceptions?
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Advertisement
Quote:Original post by Jan Wassenberg
hehe, that's quite a post :) Sadly I do not have the time to continue at such a pace; therefore, only sparse replies:


Understandable :-).

Quote:
Quote:
Quote:See how this is ballooning?

Ehm, no, I don't see

yes, you do: "This means you have a one-liner somewhere that you're going to have to remember exists". What I am pointing out is that (in response to my poking) you keep having to add more functionality/code. Here a RAII class, there a new method, then the report-error-from-dtor monstrosity - tell me with a straight face that it's all conceptually more simple than "if(err != 0) return err;".
I give you a bit of wisdom from Dijkstra: "The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague."


It's adding 5 lines of code in one spot which is then reusable everywhere else. From a USERS perspective, it is still the wonderful 1 liner it's allways been, and from that same perspective, it's conceptually simpler. Note that without RAII, if anything needs cleaning up, "if (err != 0) return err" will leak just the same as "if (err != 0) throw exception". So we turn this:

raii_scope< HANDLE , & CloseHandle > handle( CreateFile( ... ) );if ( err != 0 ) return/throw error/exception;handle.release();...


Into:

HANDLE handle = CreateFile( ... );if ( err == 0 ){    CloseHandle( handle );    handle = 0; //so we don't doubly close the handle, which would be a big error    ...}if ( handle != 0 ) CloseHandle( handle );return err;


Or:

HANDLE handle = CreateFile( ... );if ( err != 0 ){    //close all pre-existing resources here:    CloseHandle( handle );    return err;}CloseHandle( handle ); handle = 0;...return err;


This code can get much much worse in size difference as one adds more handles or error conditions to the mix.

Quote:
Quote:I've implemented exceptions in a toy compiler. I hope this qualifies me for the glorious title of knowing one of the many ways that exceptions can be implemented :-).

Ah, cool. But there's more to the story: look into SEH, which is the backend for VC's mechanism. Then you will know why throwing is so insanely slow.


I've looked into it some in the past, and although I'm having difficulty finding any major complaints over it's preformance using google, probably because I'm not using the right terms.

But if we're going to argue preformance, let's view another point.

By returning an error code, and checking this error code on every call, we incur a potentially large overhead for normal operation. This means that, although error codes outpreform exceptions when an error does occur, exceptions outpreform error codes when things succeed, which is the usual case.

This route was most likely chosen because of the fact that it speeds up the normal case. If one wanted, behind the hood, one could implement exceptions in the same way return codes are implemented - which would mean exceptions would preform exactly like return codes!

The fact, however, is that most of the time an error is NOT occuring, means that switching to exceptions can result in a net gain in terms of preformance.

Quote:
Quote:And then you ask what the name or size of the object is, right? No? Refering to something that has yet to exist is one thing. Trying to use something that has yet to exist is another.

I think you misunderstood. In our RTS game, scenarios often refer to art files that don't exist - possibly because someone forgot to commit all files. Since we're distributed over many time zones, waiting for that problem to be fixed is not an option; therefore, things must not explode when a file is missing.


Out of curiosity, how do you handle this? Load in a default piece of artwork? That's a method of error handling. One can do that with exceptions too:

struct texture { ... };struct model { texture texture; };struct scenario { vector< model > models };void load_scenario( scenario & scenario , ... ){   ...   for ( ... )   {      model model;      try      {         model.texture = load_model( ... );      }      catch( model_does_not_exist )      {         model.texture = load_model( "placeholder.bmp" );      }      scenario.models.push_back( model );   }}


Quote:
Quote:By this calculation, my computer can find the sizes of over 16 thousand files in 1 second - or in other words, it takes 16-thousandths of a second to find the size of one file. I hardly call this slow :-).

It may actually be less than that (not sure about your benchmark there), but here's the catch: our game has a VFS that needs to enumerate all data files at startup. It organizes them all in a tree, so that file open performance remains fast even if several patch archives and mods have been installed. Windows explorer tells me there are 18181 files in the binaries/ directory, so 16ms for each one would indeed end up being dog slow.


16ms == 16/1,000 of a second (sixteen thousandths of a second)
16ms != 1/16,000 of a second (one sixteen-thousandth of a second, what was meant, although quite badly said)
0.0625ms == 1/16,000 of a second.

Big difference.

At 0.0625ms/file, it'd take 1.13 seconds to check the size of each file in your binaries/ dir.
At 16ms/file, it'd take 4.8 minutes.

Note that my piece of **** of a server can preform the first of the two speeds, so I'm somehow doubtful that any modern computer is going to do it slower than that.

Quote:
Quote:I feel exactly the same with my comments, and hold the same sentiments towards your APIs, I assure you ^_^.

Ah. This is why I have refrained from touching on exception-related stuff just now - there's no point ;p

I give you one parting thought, though: imagine you have a exception-infested library. If for some reason exceptions must not reach the caller(*), you are forced to wrap every call site in catch(...) (+). Inconvenient, sloow, and you stomp on unexpected exceptions (access violation due to hardware or OS error). Nice!
Now if the library had been written with error codes (which may or may not be more work, depending on the taste of the programmer), it would be much easier to use. If you insist on getting exceptions when something goes wrong, you can write simple and more efficient if(err == WHATEVER) throw whatever(); wrappers.


inline int dispatch( boost::function< void ( void ) > function ){    try    {        function();    }    catch( const api_exception & e ) { return e.code(); } //API Exception -> Error Code    catch( std::bad_alloc ) { exit(0); } //EJECT!!! EJECT!!!    catch( ... ) { return -1; }}int API_Function( void ){    return dispatch( do_stuff );}int API_Function( int argument ){    return dispatch( bind( do_stuff , argument ) );}


Not particularly hard :-). If you want to have your library return error codes, design it so it's easy. Then again, I'm a C++ diehard. I just figure I shouldn't constrain my library just so that C users can use it. The need to target multiple languages with a restricted interface is one to be avoided, in my mind.

Quote:* excellent example: when passing across language boundaries.
+ non-sequitur: what do your exception specifications make of the fact that anything can "throw" SEH exceptions?


SEH is ignored, since I'm not programming for banking systems, nor if I was would I be programming on a windows platform. I've noticed that there are patches to add SEH to GCC out there, but I've not gone to the bother of installing any, so I don't even have it available. I use signal handlers to deal with SIGHUP and the like.

Signals are high-level events. To deal with SIGHUP I might close and reopen my listening socket (with new parameters) - which isn't something that should interrupt the flow of my other threads (it's a signal not an exception damnit!!!)

To deal with SIGBUS in your WinAmp with a playlist example, I'd have it walk through the playlist and save it to a special "to-be-recovered-from" file. This is a high level action, not something that I should be trying to unwind from a thread of execution to do.
Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

panda@industry:~$ cat test.cc
#include <signal.h>#include <exception>#include <iostream>#include <string>bool die = false;using namespace std;struct neverdie{        ~neverdie( void )        {                while( !die ) {}        }};void sigusr2handler( int ){        cout << "Recieved: SIGUSR2" << endl;}void sigusr1handler( int ){        cout << "Recieved: SIGUSR1" << endl;        cout << "Setting die to true, any neverdie s will die :-)." << endl;        die = true;}void in_a_try( void ){        cout << "Can we detect a signal within a try block? (Hit ENTER to continue)" << endl;        string line;        getline( cin , line );}void in_a_throw( void ){        cout << "Can we detect a signal while we're throwing? (Send a SIGUSR1 to coninue)" << endl;        neverdie nevereverever;        throw exception();}void in_a_catch( void ){        cout << "Can we detect a signal in a catch statement? (Hit ENTER to continue)" << endl;        string line;        getline( cin , line );}int main ( int argc , char ** argv ){        try        {                signal( SIGUSR1 , sigusr1handler );                signal( SIGUSR2 , sigusr2handler );                in_a_try();                in_a_throw();        }        catch( ... )        {                in_a_catch();        }}
panda@industry:~$ ./testCan we detect a signal within a try block? (Hit ENTER to continue)(CTRL+Z)[1]+  Stopped                 ./testpanda@industry:~$ killall -USR2 testpanda@industry:~$ fg %1./testRecieved: SIGUSR2Can we detect a signal while we're throwing? (Send a SIGUSR1 to coninue)(CTRL+Z)[1]+  Stopped                 ./testpanda@industry:~$ killall -USR1 testpanda@industry:~$ fg %1./testRecieved: SIGUSR1Setting die to true, any neverdie s will die :-).Can we detect a signal in a catch statement? (Hit ENTER to continue)(CTRL+Z)[1]+  Stopped                 ./testpanda@industry:~$ killall -USR2 testpanda@industry:~$ fg %1./testRecieved: SIGUSR2panda@industry:~$ _


(CTRL+Z) == stopping sequence, I could've run the program in the background and had the "Recieved:" messages pop up as soon as I sent off the signal had I wanted to.
And to continue on this providing more proof for my arguments streak, the crappy server can manage:

panda@industry:~$ cat test.cc
#include <time.h>#include <iostream>using namespace std;unsigned int i;int main ( void ){        clock_t begin = clock();        clock_t end = begin;        while ( end - begin < CLOCKS_PER_SEC * 10 )        {                try                {                        throw std::exception();                }                catch( std::exception e )                {                        ++i;                }                end = clock();        }        float time = ((float)(end - begin)) / CLOCKS_PER_SEC;        cout << "Time elapsed: " << time << " seconds" << endl;        cout << "Throws caught: " << i << endl;        cout << "Throws/second: " << i/time << endl;}

panda@industry:~$ ./testTime elapsed: 10 secondsThrows caught: 862242Throws/second: 86224.2


Notes: compiled in debug mode, could set a breakpoint on exception throw if wanted, benchmark is on 600mhz piece of shit server using GCC 3.3.5, think that's about it.
I think both methods have merrits.
For exception handling I like that it cxan simply things by catching all errors without having to write specific code for each function call. However I dislike that during debugging (or otherwise) it can be difficult to work out where the exception was thrown from.
Exceptions aren't a nice way to handle non-critical errors (not that this is what they are meant for). If you have:
if (!AttemptToDoThis()) if (!AttemptToDoAnotherThing())  if (!AttemptToDoYetAnotherThing())   if (!AttemptToDoOneMoreThing())    OhCrapWereGoingToDie();YayWeSucceededAtSomeLevel();
Both have their place. In some cases either is appropriate, but in other cases it would be wrong to use the least appropriate one.
I'm sure you'll both agree that when working with code that uses strictly one or the other, switching to the other means lots of "if (FAILED(hr)) _com_issue_error(hr);" type of stuff, or lots of "try { doX(); } catch () { return MY_ERR_CODE; }" which is bad.

Hey, and forget using MS Word in your arguments. It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:So we turn this: [..] Into: [mess]

*sigh* no, no, no. The C idiom is a "bailout ladder":
	err = do1()	if(err < 0)		goto exit	err = do2()	if(err < 0)		goto exit_undo1	return 0;	// successexit_undo1:	undo1();exit:	return err;

Duplicating undo1() everywhere sucks, IMO.

Quote:By returning an error code, and checking this error code on every call, we incur a potentially large overhead for normal operation. This means that, although error codes outpreform exceptions when an error does occur, exceptions outpreform error codes when things succeed, which is the usual case.[..]most of the time an error is NOT occuring, means that switching to exceptions can result in a net gain in terms of preformance.

heh, no way. Quoting a previous post:
Each routine returns an int (or: signed integral value), and has an err variable that is ideally mapped to the eax register (whee, I'm being IA-32 specific). Calls are made via a TRY macro that does err = func(); if(err < 0) return err; .
On the hardware side, this has negligible overhead - the error value isn't moved at all, since it's already returned in eax. Comparison and well-predicted, branch-not-taken costs 1 clock; if the compiler is really clever, it would set all return values via instructions that have flag side-effects, so even the comparison is eliminated.
Now that's somewhat utopic, but what I'm trying to point out is that "testing each return value" is a matter of wrapping each call in a macro, and costs 1-2 instructions | clocks. If resources are allocated, a bailout ladder would be used.
--
How does VC implement exceptions? They basically log creation/destruction of each object, so there's your nothing-was-thrown overhead right there.

Quote:Out of curiosity, how do you handle this? Load in a default piece of artwork? That's a method of error handling. One can do that with exceptions too:

Right, what I am proposing is policy and unrelated to mechanism (return or exception). BTW, you forgot error checking in the second load_model -> boom. What this example was trying to point out: this is a commonplace occurence and therefore not "exceptional".

Quote:16ms != 1/16,000 of a second (one sixteen-thousandth of a second, what was meant, although quite badly said)
0.0625ms == 1/16,000 of a second.

Ah, heh. Yeah, 16ms was a bit high. But 62µs is in turn a bit low; VC's stat() is much slower..

Quote:Note that my piece of **** of a server can preform the first of the two speeds, so I'm somehow doubtful that any modern computer is going to do it slower than that.

.. but anyway: 1130ms at startup is absolutely unacceptable for this task.

Quote:Not particularly hard :-)

I do not believe you solved the intercept-SEH-exception problem, nor the performance impact of using exceptions for all error reporting.

Quote: Then again, I'm a C++ diehard. I just figure I shouldn't constrain my library just so that C users can use it. The need to target multiple languages with a restricted interface is one to be avoided, in my mind.

I see. Diehard, eh? That explains the "zealot mind-filter" ;p
Clearly you have different requirements. At work we have a mixed Python-C++ system (a very happy marriage - productivity and speed); the abovementioned RTS game is scripted with &#106avascript. In both cases, tossing exceptions left and right is simply not possible.

Quote:To deal with SIGBUS in your WinAmp with a playlist example, I'd have it walk through the playlist and save it to a special "to-be-recovered-from" file. This is a high level action, not something that I should be trying to unwind from a thread of execution to do.

High-level, eh? When crashing, all that matters is that the user's data is written out. If phrases like 'high level' get in the way, I would hold the developer criminally negligible.

Quote:Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.

Quote:Throws/second: 86224.2

Only 7000 clocks? Not bad at all; GCC/Linux exception implementation is clearly more efficient. Do not forget, though, that there is no unwinding going on, nor are there any objects that need to be destroyed. Real-world exceptions will be much slower.

iMalc:
Quote:I think both methods have merrits.

Finally someone with sense :) Agree on all points.

Quote:It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.

Amen to that. *Relying on* such a band-aid to protect user data is the hallmark of shoddy software.
An interesting parallel is the German Torpedo Bureau in the second World War: after repeated torpedo failures, those responsible were found negligent and sentenced to prison terms.
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Quote:Original post by iMalc
I think both methods have merrits.
For exception handling I like that it cxan simply things by catching all errors without having to write specific code for each function call. However I dislike that during debugging (or otherwise) it can be difficult to work out where the exception was thrown from.


I have absolutely no problem with this, using GDB, because I can set the program to break on any exception or specific exception. Examples:

(gdb) catch throw
(gdb) catch throw std::exception

Then, the program will stop the moment that type of exception is thrown. I can then preform a backtrace:

(gdb) bt

Which will then list the call stack, complete with line numbers if I so desire.

Quote:Hey, and forget using MS Word in your arguments. It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.


Heh. Okay, so save-when-crash ;-).
Quote:Original post by Jan Wassenberg
Quote:So we turn this: [..] Into: [mess]

*sigh* no, no, no. The C idiom is a "bailout ladder":


So:

    HANDLE handle = CreateFile( ... );    if ( err != 0 ) goto undo1;    CloseHandle( handle ); //e.g. example contrived handle.release()    handle = 0; //so we don't double close    ...undo1:    if ( handle != 0 ) CloseHandle( handle );    return err;


Instead of:

raii_scope< HANDLE , & CloseHandle > handle( CreateFile( ... ) );if ( err != 0 ) return/throw error/exception;handle.release();...


The second, RAII version is still smaller.

Quote:
Quote:By returning an error code, and checking this error code on every call, we incur a potentially large overhead for normal operation. This means that, although error codes outpreform exceptions when an error does occur, exceptions outpreform error codes when things succeed, which is the usual case.[..]most of the time an error is NOT occuring, means that switching to exceptions can result in a net gain in terms of preformance.

heh, no way. Quoting a previous post:
Each routine returns an int (or: signed integral value), and has an err variable that is ideally mapped to the eax register (whee, I'm being IA-32 specific). Calls are made via a TRY macro that does err = func(); if(err < 0) return err; .
On the hardware side, this has negligible overhead - the error value isn't moved at all, since it's already returned in eax. Comparison and well-predicted, branch-not-taken costs 1 clock; if the compiler is really clever, it would set all return values via instructions that have flag side-effects, so even the comparison is eliminated.
Now that's somewhat utopic, but what I'm trying to point out is that "testing each return value" is a matter of wrapping each call in a macro, and costs 1-2 instructions | clocks. If resources are allocated, a bailout ladder would be used.


This is all good argument, but it simply states that error codes arn't much slower, rather than arn't slower period :-). If for god knows why I'm forced to have error checking in an innermost loop which happens to be a bottleneck, 1 cycle can add up.

Quote:How does VC implement exceptions? They basically log creation/destruction of each object, so there's your nothing-was-thrown overhead right there.


Well, sounds like VC sucks ;-). But seriously, that's an argument against VC's implementation of exceptions, not exceptions in general.

Quote:
Quote:Out of curiosity, how do you handle this? Load in a default piece of artwork? That's a method of error handling. One can do that with exceptions too:

Right, what I am proposing is policy and unrelated to mechanism (return or exception). BTW, you forgot error checking in the second load_model -> boom.


I intentionally left it out, actually. If there's no placeholder.bmp, then something's seriously messed up and needs looking into. Worst case scenario, it was just pure coincidence, programmer plays around in paint for 5 seconds, and saves placeholder.bmp :-).

Quote:What this example was trying to point out: this is a commonplace occurence and therefore not "exceptional".


Commonplace for a project does not mean commonplace on a per-call level. If a function is run 1 million times each time, and on average, one of those calls fails, then it's commonplace for that project to have that occur. On the other hand, from the function's perspective, it's an extremely rare exceptional circumstance which only happens 1 in a million times.

Quote:
Quote:16ms != 1/16,000 of a second (one sixteen-thousandth of a second, what was meant, although quite badly said)
0.0625ms == 1/16,000 of a second.

Ah, heh. Yeah, 16ms was a bit high. But 62µs is in turn a bit low; VC's stat() is much slower..


Either VC's fault, your hard drive's fault, or your file system's fault. For either of the latter two, this will likely be offset partially as the file is cached in memory, assuming one accesses it.

Quote:
Quote:Note that my piece of **** of a server can preform the first of the two speeds, so I'm somehow doubtful that any modern computer is going to do it slower than that.

.. but anyway: 1130ms at startup is absolutely unacceptable for this task.


Assuming none of this is time is from actually waiting on reading from the hard drive, data which will be cached, and thus the penalty incurred only once when reading.

Quote:
Quote:Not particularly hard :-)

I do not believe you solved the intercept-SEH-exception problem, nor the performance impact of using exceptions for all error reporting.


One problem at a time, eh? :-).

Quote:
Quote:Then again, I'm a C++ diehard. I just figure I shouldn't constrain my library just so that C users can use it. The need to target multiple languages with a restricted interface is one to be avoided, in my mind.

I see. Diehard, eh? That explains the "zealot mind-filter" ;p


Diehard in the sense that my requirements are for myself, and that I'm not going to limit myself to a reduced functionality in an extremely versitile language. I havn't found real problems reimplementing the functionality found in other languages - it might be more verbose (functors for lambada, weird crazy ass templates for smalltalkesque syntax) but it works :-).

Quote:Clearly you have different requirements. At work we have a mixed Python-C++ system (a very happy marriage - productivity and speed); the abovementioned RTS game is scripted with &#106avascript. In both cases, tossing exceptions left and right is simply not possible.


In a multi-language environment I could see this being more of a problem. I have high enough productivity and flexibility in C++ that I can adapt the padigrams of other languages, rather than depending on actually having the language to use it.

Quote:
Quote:To deal with SIGBUS in your WinAmp with a playlist example, I'd have it walk through the playlist and save it to a special "to-be-recovered-from" file. This is a high level action, not something that I should be trying to unwind from a thread of execution to do.

High-level, eh? When crashing, all that matters is that the user's data is written out. If phrases like 'high level' get in the way, I would hold the developer criminally negligible.

It wouldn't get in the way, because I'd use the sane way to deal with things - a signal handler.

Quote:
Quote:Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.


Unless, of course, the example compiles on Windows. Which it does.

Quote:
Quote:Throws/second: 86224.2

Only 7000 clocks? Not bad at all; GCC/Linux exception implementation is clearly more efficient. Do not forget, though, that there is no unwinding going on, nor are there any objects that need to be destroyed. Real-world exceptions will be much slower.


You have unwinding with your bailout ladder, too. Real-world exceptions will also have optimizations turned on.

Quote:iMalc:
Quote:I think both methods have merrits.

Finally someone with sense :) Agree on all points.

Quote:It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.

Amen to that. *Relying on* such a band-aid to protect user data is the hallmark of shoddy software.


Or shoddy wiring. Or shoddy environment (read: Windows). Or that annoying little brother. Or your cat, if it likes to chew on wires. There's half a bazillion things that can go wrong which can cause you to loose a file, after all :-). Of course I won't deny that Word is one of the epitomies (sp?) of bad software, but that "band-aid" works just as well for hardware failures and OS bugs, which is where any sort of after-the-fact system is going to have a big chance to fail.

Quote:An interesting parallel is the German Torpedo Bureau in the second World War: after repeated torpedo failures, those responsible were found negligent and sentenced to prison terms.


Yes, because we're programming torpedos </sarcasm>.
Quote:The second, RAII version is still smaller.

Duh! Are you surprised?

Quote:This is all good argument, but it simply states that error codes arn't much slower, rather than arn't slower period :-). If for god knows why I'm forced to have error checking in an innermost loop which happens to be a bottleneck, 1 cycle can add up.

*sigh* You really think exceptions are faster? I mean, that's just amazing. For someone who has implemented them, you really have no clue.

Quote:Well, sounds like VC sucks ;-). But seriously, that's an argument against VC's implementation of exceptions, not exceptions in general.

I was under the impression that this was the standard approach. See the end of the other thread, which gives performance and implementation data.

Quote:I intentionally left it out, actually. If there's no placeholder.bmp, then something's seriously messed up and needs looking into. Worst case scenario, it was just pure coincidence, programmer plays around in paint for 5 seconds, and saves placeholder.bmp :-).

Riight.
I've had bugs in my Zip IO code (part of VFS) that only failed on files with certain offsets. You do not want such a bug to take down the entire game. Which it would here, since that may throw an exception which you don't catch (=> boom).

Quote:Commonplace for a project does not mean commonplace on a per-call level. If a function is run 1 million times each time, and on average, one of those calls fails, then it's commonplace for that project to have that occur. On the other hand, from the function's perspective, it's an extremely rare exceptional circumstance which only happens 1 in a million times.

It's great that you can make everything seem small by pulling "million" out of your hat. Point is taken, but when writing a library/code module, you can't assume exactly how often you will be called. It seems quite arrogant to say "all errors are exceptions".

Quote:One problem at a time, eh? :-).

hehe. Well, as we stand, these problems leave your exception->error thunks not suitable for production use.

Quote:Diehard in the sense that my requirements are for myself, and that I'm not going to limit myself to a reduced functionality in an extremely versitile language. I havn't found real problems reimplementing the functionality found in other languages - it might be more verbose (functors for lambada, weird crazy ass templates for smalltalkesque syntax) but it works :-).

Let me guess - you really only use (i.e. know well enough) C++? Now obviously all non-toy languages are Turing complete, so anything you can do in one you can emulate in another. But that does not address the fact that certain tasks are much easier in higher-level languages.

Quote: I have high enough productivity and flexibility in C++ that I can adapt the padigrams of other languages, rather than depending on actually having the language to use it.

"*Enough* productivity and flexibility", heh. Someone using assembly language could say that of themselves. Now it's great to adapt useful things from other languages, but you can't say with a straight face that Python offers no advantages over C++.

Quote:It wouldn't get in the way, because I'd use the sane way to deal with things - a signal handler.

Oops, those aren't raised on Windows. What do you do then?

Quote:
Quote:
Quote:Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.

Unless, of course, the example compiles on Windows. Which it does.

You misunderstand. Since signals are not the delivery vehicle for access violation notifications on Windows, whether or not they arrive is irrelevant.

Quote:You have unwinding with your bailout ladder, too. Real-world exceptions will also have optimizations turned on.

Duh.

Quote:There's half a bazillion things that can go wrong which can cause you to loose a file, after all :-).

What does that have to do with our discussion?! But hey, if a bug destroys user work, at least you can tell them: "maybe your cat would have also caused it".

Quote:but that "band-aid" works just as well for hardware failures and OS bugs, which is where any sort of after-the-fact system is going to have a big chance to fail.

This obviously went right past you. A safety net is great, but you still don't want to *rely on* (<- did you spot the emphasis this time?) it by being careless on the tightrope.

Quote:Yes, because we're programming torpedos </sarcasm>.

Sarcasm is not an adequate reply. And speak for yourself - if at work we screw up, people run into walls (ouch) and bang up a rather expensive helmet.
It would do the industry good to assume more responsibility than "manajmint not responsible fer nuthin'".
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Quote:Original post by Jan Wassenberg
Quote:The second, RAII version is still smaller.

Duh! Are you surprised?

Point is, if we're going to talk about the horrible ballooning effects of adding 5 lines of code elsewhere, we shouldn't ignore the fact that we're saving 5 lines of code in two uses as well.

Quote:
Quote:This is all good argument, but it simply states that error codes arn't much slower, rather than arn't slower period :-). If for god knows why I'm forced to have error checking in an innermost loop which happens to be a bottleneck, 1 cycle can add up.

*sigh* You really think exceptions are faster? I mean, that's just amazing. For someone who has implemented them, you really have no clue.

You said it yourself ;-). It dosn't even have to be in an innermost loop, if every single function call you make has return codes to keep track of. Is the difference small enough that it's not going to be a main arguing point? Probably. Is the difference small enough that it won't offset the overhead during a throw, making the argument a moot point? That will depend on usage...

Quote:
Quote:Well, sounds like VC sucks ;-). But seriously, that's an argument against VC's implementation of exceptions, not exceptions in general.

I was under the impression that this was the standard approach. See the end of the other thread, which gives performance and implementation data.


Program         Code Size    Time (no throws)   Time (with throws)	XBench.c        4.6k         1392 ms            1362 ms	CPPBench.cpp;   35.3k        1492 ms            71343 ms
Of course, you just took the code without checking that these benchmarks were on a decent compiler and recent. Compiling them myself, we get:
CPPBench.cpp;   35.3k          47 ms             1140 ms
Notes:

1) This is in release mode, exceptions preform the same either way, nothrow preforms faster in release.
2) This benchmark compares running normally every time to throwing an exception every time. Exceptions are not meant to fail 25 billion times a frame, this is not where they excell. Even so, they only preform 25x slower than a simple divide, a few adds, and a return, when we're re-entering our try/catch block, throwing an exception, and appropriately catching it every frame!
3) Yes, I've allready admitted that exceptions preform slower when they allways fail. For fair comparison, one must look at how often the operation will fail, and compare that to the speed gained when they don't fail.

Quote:
Quote:I intentionally left it out, actually. If there's no placeholder.bmp, then something's seriously messed up and needs looking into. Worst case scenario, it was just pure coincidence, programmer plays around in paint for 5 seconds, and saves placeholder.bmp :-).

Riight.
I've had bugs in my Zip IO code (part of VFS) that only failed on files with certain offsets. You do not want such a bug to take down the entire game. Which it would here, since that may throw an exception which you don't catch (=> boom).


It'd be caught in my main handler, like all such "missed" exceptions are. If you'd rather replace it with a whitespace texture, and maybe adding a log entry, then do that. Me? If the bitmap that clearly indicates a resource is missing is in and of itself missing, I want the fact uncovered, rather than running around prehaps in my rendering code wondering why the screen is all white, when in fact some goofball thought it'd be funny to not commit ANY of the textures of a scenario someday.

Quote:
Quote:Commonplace for a project does not mean commonplace on a per-call level. If a function is run 1 million times each time, and on average, one of those calls fails, then it's commonplace for that project to have that occur. On the other hand, from the function's perspective, it's an extremely rare exceptional circumstance which only happens 1 in a million times.

It's great that you can make everything seem small by pulling "million" out of your hat. Point is taken, but when writing a library/code module, you can't assume exactly how often you will be called.
But you can make educated guesses. Considering how much of my code never throws period, 1 in a million would be a conservative measure.
Quote:It seems quite arrogant to say "all errors are exceptions".
Not all are, but the ones you don't deal with are. If you don't want it to be an exception, head it off at the source. Check that a file exists before trying to open it, and whine in a dialog for the user when it dosn't. Whine again when he types in 1.2.3.4.5 for an IP address. If you've tried to open a file that dosn't exist, it's obvious you havn't planned for that contingency, and therefor, this problem is unexpected - which is when I throw an exception. If this causes 20 billion exceptions to be called a second, then that's a bug that needs fixing, because it's become the rule rather than the exception.

Quote:
Quote:One problem at a time, eh? :-).

hehe. Well, as we stand, these problems leave your exception->error thunks not suitable for production use.

Which is why I don't use them. For dtor errors, I simply log them and/or assert them.
Quote:
Quote:Diehard in the sense that my requirements are for myself, and that I'm not going to limit myself to a reduced functionality in an extremely versitile language. I havn't found real problems reimplementing the functionality found in other languages - it might be more verbose (functors for lambada, weird crazy ass templates for smalltalkesque syntax) but it works :-).

Let me guess - you really only use (i.e. know well enough) C++? Now obviously all non-toy languages are Turing complete, so anything you can do in one you can emulate in another. But that does not address the fact that certain tasks are much easier in higher-level languages.


I've programmed in many flavors of BASIC (Q, VB, the "real" kind on the apple 2 - built a tanks game with destructable terrain using the first one), x86 assembly, plenty of different shells, XSL (if that counts), PHP (written online file managers using it), and would be programming in smalltalk probably right now if I hadn't simply ripped off some of the ideas and ported them to C++, where my pre-existing library is.

I feel that I can approach C++ as one of the highest-level languages out there. When I'm missing a feature, I can implement it. Array slicing? boost::range. Lambada? functors or boost::lambada. N-greater-than-single-dynamic-dispatch? Hack out some of the type macros from the boost::function source and do it. Signals and slots? Again, boost.

Quote: I have high enough productivity and flexibility in C++ that I can adapt the padigrams of other languages, rather than depending on actually having the language to use it.

"*Enough* productivity and flexibility", heh. Someone using assembly language could say that of themselves. Now it's great to adapt useful things from other languages, but you can't say with a straight face that Python offers no advantages over C++.

Wrong emphisis, and no I can't. At the same time, I can't say Python offers enough advantages over C++ and PHP for me to not use one of them. Even BASIC has it's advantages. You don't see me writing it anymore though, do you?

Quote:
Quote:It wouldn't get in the way, because I'd use the sane way to deal with things - a signal handler.

Oops, those aren't raised on Windows. What do you do then?

Quote:
Quote:
Quote:Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.

Unless, of course, the example compiles on Windows. Which it does.

You misunderstand. Since signals are not the delivery vehicle for access violation notifications on Windows, whether or not they arrive is irrelevant.


I was under the impression that signals still worked. My compile test proves me right:

#include <signal.h>#include <iostream>using namespace std;#define SIGSEGV      11        //C void handler_sigsegv( int ){	cout << "We caught a SIGSEGV, aka Segfault or Access Violation" << endl;}int main ( int argc , char ** argv ){	signal( SIGSEGV , handler_sigsegv );	cout << "About to access 0x00000000..." << endl;	*((int *)0x00000000) = 0;	cout << "Accessed 0x00000000!!!" << endl;}
C:\eclipse\workspace\test.2>Debug\test.2About to access 0x00000000...We caught a SIGSEGV, aka Segfault or Access ViolationC:\eclipse\workspace\test.2>_


Funny, that.

Quote:
Quote:You have unwinding with your bailout ladder, too. Real-world exceptions will also have optimizations turned on.

Duh.


So, same overhead, and converging times since the difference between them remains a constant.

Quote:
Quote:There's half a bazillion things that can go wrong which can cause you to loose a file, after all :-).

What does that have to do with our discussion?! But hey, if a bug destroys user work, at least you can tell them: "maybe your cat would have also caused it".

*sigh* I'm simply trying to point out that auto-save is a valid technique for helping prevent user data loss. No, your program should preferibly not be buggy, but I have no faith in Microsoft to be able to pull that off, and if they're going to give me a buggy piece of ****, I'd much prefer it have auto-save stuck on as a bandaid, which will also protect against said cat, than just loose my work every 5 minutes.

Quote:
Quote:but that "band-aid" works just as well for hardware failures and OS bugs, which is where any sort of after-the-fact system is going to have a big chance to fail.

This obviously went right past you. A safety net is great, but you still don't want to *rely on* (<- did you spot the emphasis this time?) it by being careless on the tightrope.

Being careless on the tightrope is stupid, yes. It's even stupider to assume that you're a god with no need for a safety net.
Quote:
Quote:Yes, because we're programming torpedos </sarcasm>.

Sarcasm is not an adequate reply. And speak for yourself - if at work we screw up, people run into walls (ouch) and bang up a rather expensive helmet.
It would do the industry good to assume more responsibility than "manajmint not responsible fer nuthin'".

Oh I'll agree with that. But the point remains, I'm not programming torpedos, You (havn't mentioned any projects where you) are programming torpedos, and my Cat dosn't exist, so even if he were a super-kitty, he wouldn't be programming no torpedos neither ;-).

Rely on bandaids? I wouldn't, because it's a bad idea.
Don't use a safey net because it's a bandaid? I wouldn't do that either.

There's a difference between not working on bug fixing because you've got a safety net, and realizing the fact that you're only human. No, I think it'd be pretty stupid to rely on having auto-save exist for the sole purpouse of dealing with that bug you couldn't track down. But yes, I think it'd be a solution to hardware failures (one of the many things that can send out signals). If you want to set up a signal handler to deal with specialized contigencies, the more power to you. Just realize that you can't plan for, and deal with, every one outside of a controlled environment.

This topic is closed to new replies.

Advertisement