www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Are D Exceptions Fast and Efficient?

reply AJG <AJG_member pathlink.com> writes:
Hi there,

What is the cost (performance & memory-wise) of throwing and catching an
exception in D? First, why do I ask this:

1) Exceptions are actually a great way to handle errors.
2) Thus, it would be great to handle ALL errors via exceptions.
Gets rid of return codes, errnos, null-checking, etc.
3) However, error-handling MUST be fast and efficient.
Particularly errors that you expect will happen relatively commonly.
4) Hence, none of this will work if exceptions are slow.

Why I'm I concerned?

Well, in C#, which is pretty cool (but managed) and has structured exception
handling, there is one nasty restriction:

Exceptions are _clearly_ only meant for "exceptional" situations. Things that
shouldn't happen a lot: running out of memory, database connection failed, etc.

This restriction is due to [a] various authors recommending such design;
understandable, given [b] the fact that when an exception is thrown, your
program halts execution literally for a couple of _human_ seconds.

This is simply unacceptable for a simple thing like an "invalid input string."
So in C#, we are forced to do "efficient" error checking _before_ a potential
exception is thrown. In essence, exceptions in C# are a last resort.

So, to recap, is it possible to handle all errors in D via exceptions (without a
speed cost), or should the C# approach be taken: using exceptions only for
exceptional stuff (a limitation, IMHO)?

Thanks!
--AJG.

2B || !2B, that is the question.
================================
Jun 28 2005
next sibling parent reply Mike Capp <mike.capp gmail.com> writes:
In article <d9s10t$1lid$1 digitaldaemon.com>, AJG says...
Well, in C#, [...]
Exceptions are _clearly_ only meant for "exceptional" situations. Things that
shouldn't happen a lot: running out of memory, database connection failed, etc.
[...] when an exception is thrown, your
program halts execution literally for a couple of _human_ seconds.

I find this very surprising. We've been writing downright exception-happy C# for a couple of years now, including things like dodgy per-cell string parsing inside large data tables, and have never encountered any such problems. C++ implementations often exhibit a severe pause if they defer loading EH tables in from disk until the first throw, but that's a deterministic-destruction overhead. I wouldn't expect to see it in a GC language like C#. (Or D.) cheers, Mike
Jun 28 2005
parent AJG <AJG_member pathlink.com> writes:
Hi there,

I think I need to be stealing your C# exception code ;) You must be doing
something right if it's relatively fast like that. I'm coding in C# right now (a
business app) and a particular loop is throwning an IndexOutOfRangeException.

No matter where I catch it, it makes the program choke at least 2-3 seconds. If
I let it go unhandled, it will be even worse. This is the case too every other
time I've dealt with exceptions.

Anyway, I digress. But I simply can't figure how you've never run into the
infamous halt after an exception is thrown.

Cheers,
--AJG.


In article <d9s26c$1mgs$1 digitaldaemon.com>, Mike Capp says...
In article <d9s10t$1lid$1 digitaldaemon.com>, AJG says...
Well, in C#, [...]
Exceptions are _clearly_ only meant for "exceptional" situations. Things that
shouldn't happen a lot: running out of memory, database connection failed, etc.
[...] when an exception is thrown, your
program halts execution literally for a couple of _human_ seconds.

I find this very surprising. We've been writing downright exception-happy C# for a couple of years now, including things like dodgy per-cell string parsing inside large data tables, and have never encountered any such problems. C++ implementations often exhibit a severe pause if they defer loading EH tables in from disk until the first throw, but that's a deterministic-destruction overhead. I wouldn't expect to see it in a GC language like C#. (Or D.) cheers, Mike

Jun 28 2005
prev sibling next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"AJG" <AJG_member pathlink.com> wrote in message 
news:d9s10t$1lid$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D?

I refer you to the "Error handling" section of D's spec (http://www.digitalmars.com/d/index.html): " Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected. D exception handling fits right in with that. Because errors are unusual, execution of error handling code is not performance critical. Exception handling stack unwinding is a relatively slow process. The normal flow of program logic is performance critical. Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors. " For one, D assumes that exceptions are very unusual circumstances. For two, it says that "error handling code is not performance critical." Now, it won't pause your app for a few seconds when throwing an exception like in .NET, but it won't be _as fast_ as the rest of your code. But really, the question is - why would you design an application to throw an exception for anything _other_ than absolutely exceptional circumstances? I think it's more of a design issue than anything.
Jun 28 2005
next sibling parent reply Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
Jarrett Billingsley wrote:
 For one, D assumes that exceptions are very unusual circumstances.  For
 two, it says that "error handling code is not performance critical."

This is only theory. Throwing and handling exceptions should be (and in fact is) as fast as possible.
 Now, it won't pause your app for a few seconds when throwing an exception
 like in .NET, but it won't be _as fast_ as the rest of your code.

 But really, the question is - why would you design an application to throw
 an exception for anything _other_ than absolutely exceptional
 circumstances? I think it's more of a design issue than anything.

Why? Exceptions are natural way of handling almost _every_ _error_. I can't see why someone would resignate from using them just because "they are not performance critical". In the real code execeptions may save a lot of developer time and program time. They are cheaper and faster. Here are two examples: Example 1. void doSomething(){ for(int i=0;i<SOMEBIGCONST;i++) if(ERRORCODE == doSubpart()) handleError(); } vs. void doSomething(){ try{ for(int i=0;i<SOMEBIGCONST;i++) doSubpart(); }catch(Error e){ handleError(); } } As you can see in second one you saves SOMEBIGCONST number of comparing command. And if Errors are realy rare, even if throwing&handling of exception isn't "performance critical" you're getting more speed. Example 2. (this one isn't so easy, but I hope you'll get the idea) void doFoo(){ if(ERROR == doBoo()){ handleExeption(); // and let's say ... try again doBoo()... // (I will not write code for that) } } int doBoo(){ if(ERROR == doMoo()) return ERROR; } } int doMoo(){ if(ERROR == doQoo()) return ERROR; } } ind doQoo(){ /* and goo one with that sicknes ... */ } vs void doFoo(){ try{ doMoo(); }catch(Error e){ handlerException(); } } void doBoo(){ doBoo(); } void doQoo(){ /* and go one where there will be part that can create error and throw it*/ } As doFoo() is the only function that know how to handle Error only it is interested in handling it. The rest of code don't have to worry about it because it don't care. Precious CPU cycles are saved. End of examples. This is real example of fact that "premature optymalization is the reason of all evil". Saying that using exceptions is not smart because they are not "performance critical" is such premature optymalization. Exceptions are created for handling errors. And I'll be tring to use them as much as I can for that. Using exceptions is faster and cheaper (in all aspects). And only when profiler will say that some part of my code can benefit from using errorcodes I will use them. This is how things should be done IMO. Hell - even if they were slow as turtle I would use them. Can't you feel the magic? :) -- Dawid Ciężarkiewicz | arael
Jun 28 2005
parent reply Sean Kelly <sean f4.ca> writes:
In article <d9s92i$1v18$1 digitaldaemon.com>, Dawid
=?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= says...
Jarrett Billingsley wrote:
 For one, D assumes that exceptions are very unusual circumstances.  For
 two, it says that "error handling code is not performance critical."

This is only theory. Throwing and handling exceptions should be (and in fact is) as fast as possible.

Anyone interested in performance issues related to exception handing might enjoy this paper: http://www.research.att.com/~bs/performanceTR.pdf Sean
Jun 28 2005
parent AJG <AJG_member pathlink.com> writes:
Wow, that is a lot of info. I'm only about 20% into it; anyone care to make a
succint analysis and report it for us lazy people?

Also, there's very little mention of specific compilers (and in particular,
their specific -Optimize switches). Wouldn't this make a heck of a lot of
difference?

--AJG. 


In article <d9sdkt$22h1$1 digitaldaemon.com>, Sean Kelly says...
In article <d9s92i$1v18$1 digitaldaemon.com>, Dawid
=?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= says...
Jarrett Billingsley wrote:
 For one, D assumes that exceptions are very unusual circumstances.  For
 two, it says that "error handling code is not performance critical."

This is only theory. Throwing and handling exceptions should be (and in fact is) as fast as possible.

Anyone interested in performance issues related to exception handing might enjoy this paper: http://www.research.att.com/~bs/performanceTR.pdf Sean

================================ 2B || !2B, that is the question.
Jun 28 2005
prev sibling parent reply AJG <AJG_member pathlink.com> writes:
Hi Jarrett (and others too, please do read on),

Thanks for the info. I was hoping some of the things in the spec were outdated,
and I still hope it's the case, because exceptions are too powerful to relagate
to exceptional situations only.

I think in a way the spec contradicts itself. First, it list all the things it
wants to eliminate:
- Returning a NULL pointer. 
- Returning a 0 value. 
- Returning a non-zero error code. 
- Requiring errno to be checked. 
- Requiring that a function be called to check if the previous function failed

But then it goes on to say what you quoted, which makes it all irrelevant,
because you are almost never going to encounter these "errors" anyway.

But really, the question is - why would you design an application to throw 
an exception for anything _other_ than absolutely exceptional circumstances? 
I think it's more of a design issue than anything. 

I disagree here. I think errors happen _all the time_, and they _are_ part of the normal program flow. If we use exceptions only as recommended (a very limited subset of errors, the exceptional "catastrophic" ones), we are back to the very "tedious error handling code" we set out to remove in the first place. It's back to the same ol' dirty clutter. Let me give you an example. Suppose I am building an application that requires the user to input a sequence of integers, and it does some processing with them; say, from the command-line. Here it is, with no "handling" in place at all: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # } # } Now, in an ideal world, the user will enter perfectly formatter integers, without a single mistake, and it would all be dandy. But that's not going to happen. And you _know_ that, so you _expect_ an error of this kind. It _is_ part of the regular program flow. Luckily for us, exceptions come into play and save this "quick-and-dirty" utility. If you enter a bunch of letters as an integer, toInt will not choke and segfault. It will throw an exception, which prevents it from going further and damaging anything or producing erroneous output. Since we did not set up any handling (a catch), the program will exit if any input is badly formatted. However, we can take this a step further and make the utility more robust and more useful: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # try { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # catch (Exception e) { # printf("Badly formatted input [#%d]=%.*s\n", index, arg); # printf("Exception says: %.*s\n", e.toString); # } # } # } Now, the program will catch any errors neatly, and continue normal operation (which is perfectly valid). Now ask yourself, how many times will a human being type a number incorrectly? I think we know the answer is _all the time_. What does this mean? We should _use_ the exception mechanism to replace all error handling and checking, but in order to do this, it must be relatively fast. Maybe not as fast as a simple return code, but not an order of magnitude slower either. In this example it doesn't matter much, but what if it's a million inputs? or what if it's a server handling hundreds of thousands of requests? This is all just IMHO, but I think it makes sense. Look at the traditional alternative: You must manually check all inputs beforehand, before even attempting to convert them, to see if they are perfectly formatted. You must introduce an error code return and move the output to its own ref parameter, because the error code could be mistaken for correct output. I think this is futile and redundant; you would be essentially re-writing the toInt function yourself just so that it won't throw an exception. Must one re-invent the wheel? The point of this whole thing: Exceptions are good. They are simple to use and powerful. They make you code neater and reduce useless clutter. Moreover, they represent things that occur commonly. So, we should make use of them whenever possible, and D should make them fast so that we can do this without incurring a penalty. Thanks for listening, --AJG.
"AJG" <AJG_member pathlink.com> wrote in message 
news:d9s10t$1lid$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D?

I refer you to the "Error handling" section of D's spec (http://www.digitalmars.com/d/index.html): " Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected. D exception handling fits right in with that. Because errors are unusual, execution of error handling code is not performance critical. Exception handling stack unwinding is a relatively slow process. The normal flow of program logic is performance critical. Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors. " For one, D assumes that exceptions are very unusual circumstances. For two, it says that "error handling code is not performance critical." Now, it won't pause your app for a few seconds when throwing an exception like in .NET, but it won't be _as fast_ as the rest of your code.

Jun 28 2005
parent reply "Regan Heath" <regan netwin.co.nz> writes:
Ok, so I figured why not try and time the exception handling. Here is what  
I came up with:

import std.stdio;
import std.string;
import std.c.time;
extern(C)
{
	struct timeb
	{
		time_t time;
		ushort millitm;
		short timezone, dstflag;
	}

	void _ftime(timeb *);
	alias _ftime ftime;
}

double diff(timeb* start, timeb* finish)
{
	double d;
	
	d =  finish.time-start.time;
	d += (cast(double)(finish.millitm-start.millitm))/1000.0;
	return d;
}

const int ITERATIONS = 1000000;

void throws(int i)
{
	throw new Exception(toString(i));
}

void intermediate(int i)
{
	throws(i);
}

void intermediate2(int i)
{
	intermediate(i);
}

void intermediate3(int i)
{
	intermediate2(i);
}

int returns(int i)
{
	return i;
}

void main()
{
	timeb start;
	timeb finish;
	int r;
	
	ftime(&start);	
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			r = returns(i);
		}
		catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," error codes returned in  
(",diff(&start,&finish),")");
	
	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			throws(i);
		}
		catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");
	
	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			intermediate(i);
		}
			catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");

	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			intermediate2(i);
		}
			catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");

	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			intermediate3(i);
		}
			catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");
}

Giving me these results:

C:\Library\D\src\temp>extime
1000000 error codes returned in (0.016)
1000000 exceptions thrown and caught in (7.125)
1000000 exceptions thrown and caught in (6.969)
1000000 exceptions thrown and caught in (6.969)
1000000 exceptions thrown and caught in (7.109)

In other words approx 7s for exception handling and 16 milliseconds for  
return codes (likely this equals the time required to call the functions  
in both cases). Exception handling is then approximately 437 times slower.  
A single exception took approx 7/1000 of a milliseccond.

Regan

On Wed, 29 Jun 2005 05:06:02 +0000 (UTC), AJG <AJG_member pathlink.com>  
wrote:

 Hi Jarrett (and others too, please do read on),

 Thanks for the info. I was hoping some of the things in the spec were  
 outdated,
 and I still hope it's the case, because exceptions are too powerful to  
 relagate
 to exceptional situations only.

 I think in a way the spec contradicts itself. First, it list all the  
 things it
 wants to eliminate:
 - Returning a NULL pointer.
 - Returning a 0 value.
 - Returning a non-zero error code.
 - Requiring errno to be checked.
 - Requiring that a function be called to check if the previous function  
 failed

 But then it goes on to say what you quoted, which makes it all  
 irrelevant,
 because you are almost never going to encounter these "errors" anyway.

 But really, the question is - why would you design an application to  
 throw
 an exception for anything _other_ than absolutely exceptional  
 circumstances?
 I think it's more of a design issue than anything.

I disagree here. I think errors happen _all the time_, and they _are_ part of the normal program flow. If we use exceptions only as recommended (a very limited subset of errors, the exceptional "catastrophic" ones), we are back to the very "tedious error handling code" we set out to remove in the first place. It's back to the same ol' dirty clutter. Let me give you an example. Suppose I am building an application that requires the user to input a sequence of integers, and it does some processing with them; say, from the command-line. Here it is, with no "handling" in place at all: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # } # } Now, in an ideal world, the user will enter perfectly formatter integers, without a single mistake, and it would all be dandy. But that's not going to happen. And you _know_ that, so you _expect_ an error of this kind. It _is_ part of the regular program flow. Luckily for us, exceptions come into play and save this "quick-and-dirty" utility. If you enter a bunch of letters as an integer, toInt will not choke and segfault. It will throw an exception, which prevents it from going further and damaging anything or producing erroneous output. Since we did not set up any handling (a catch), the program will exit if any input is badly formatted. However, we can take this a step further and make the utility more robust and more useful: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # try { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # catch (Exception e) { # printf("Badly formatted input [#%d]=%.*s\n", index, arg); # printf("Exception says: %.*s\n", e.toString); # } # } # } Now, the program will catch any errors neatly, and continue normal operation (which is perfectly valid). Now ask yourself, how many times will a human being type a number incorrectly? I think we know the answer is _all the time_. What does this mean? We should _use_ the exception mechanism to replace all error handling and checking, but in order to do this, it must be relatively fast. Maybe not as fast as a simple return code, but not an order of magnitude slower either. In this example it doesn't matter much, but what if it's a million inputs? or what if it's a server handling hundreds of thousands of requests? This is all just IMHO, but I think it makes sense. Look at the traditional alternative: You must manually check all inputs beforehand, before even attempting to convert them, to see if they are perfectly formatted. You must introduce an error code return and move the output to its own ref parameter, because the error code could be mistaken for correct output. I think this is futile and redundant; you would be essentially re-writing the toInt function yourself just so that it won't throw an exception. Must one re-invent the wheel? The point of this whole thing: Exceptions are good. They are simple to use and powerful. They make you code neater and reduce useless clutter. Moreover, they represent things that occur commonly. So, we should make use of them whenever possible, and D should make them fast so that we can do this without incurring a penalty. Thanks for listening, --AJG.
 "AJG" <AJG_member pathlink.com> wrote in message
 news:d9s10t$1lid$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching  
 an
 exception in D?

I refer you to the "Error handling" section of D's spec (http://www.digitalmars.com/d/index.html): " Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected. D exception handling fits right in with that. Because errors are unusual, execution of error handling code is not performance critical. Exception handling stack unwinding is a relatively slow process. The normal flow of program logic is performance critical. Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors. " For one, D assumes that exceptions are very unusual circumstances. For two, it says that "error handling code is not performance critical." Now, it won't pause your app for a few seconds when throwing an exception like in .NET, but it won't be _as fast_ as the rest of your code.


Jun 29 2005
next sibling parent reply "Regan Heath" <regan netwin.co.nz> writes:
I have no idea of the validity of these findings. I really have no idea  
what exactly exception handling does behind the scenes, I was going to  
experiment with adding variables to the stack, and heap in the functions  
to see if it made any difference.

Regan

On Wed, 29 Jun 2005 21:03:53 +1200, Regan Heath <regan netwin.co.nz> wrote:
 Ok, so I figured why not try and time the exception handling. Here is  
 what I came up with:

 import std.stdio;
 import std.string;
 import std.c.time;
 extern(C)
 {
 	struct timeb
 	{
 		time_t time;
 		ushort millitm;
 		short timezone, dstflag;
 	}

 	void _ftime(timeb *);
 	alias _ftime ftime;
 }

 double diff(timeb* start, timeb* finish)
 {
 	double d;
 	
 	d =  finish.time-start.time;
 	d += (cast(double)(finish.millitm-start.millitm))/1000.0;
 	return d;
 }

 const int ITERATIONS = 1000000;

 void throws(int i)
 {
 	throw new Exception(toString(i));
 }

 void intermediate(int i)
 {
 	throws(i);
 }

 void intermediate2(int i)
 {
 	intermediate(i);
 }

 void intermediate3(int i)
 {
 	intermediate2(i);
 }

 int returns(int i)
 {
 	return i;
 }

 void main()
 {
 	timeb start;
 	timeb finish;
 	int r;
 	
 	ftime(&start);	
 	for(int i = 0; i < ITERATIONS; i++) {
 		try {
 			r = returns(i);
 		}
 		catch (Exception e) {
 		}
 	}
 	ftime(&finish);
 	writefln(ITERATIONS," error codes returned in  
 (",diff(&start,&finish),")");
 	
 	ftime(&start);
 	for(int i = 0; i < ITERATIONS; i++) {
 		try {
 			throws(i);
 		}
 		catch (Exception e) {
 		}
 	}
 	ftime(&finish);
 	writefln(ITERATIONS," exceptions thrown and caught in  
 (",diff(&start,&finish),")");
 	
 	ftime(&start);
 	for(int i = 0; i < ITERATIONS; i++) {
 		try {
 			intermediate(i);
 		}
 			catch (Exception e) {
 		}
 	}
 	ftime(&finish);
 	writefln(ITERATIONS," exceptions thrown and caught in  
 (",diff(&start,&finish),")");

 	ftime(&start);
 	for(int i = 0; i < ITERATIONS; i++) {
 		try {
 			intermediate2(i);
 		}
 			catch (Exception e) {
 		}
 	}
 	ftime(&finish);
 	writefln(ITERATIONS," exceptions thrown and caught in  
 (",diff(&start,&finish),")");

 	ftime(&start);
 	for(int i = 0; i < ITERATIONS; i++) {
 		try {
 			intermediate3(i);
 		}
 			catch (Exception e) {
 		}
 	}
 	ftime(&finish);
 	writefln(ITERATIONS," exceptions thrown and caught in  
 (",diff(&start,&finish),")");
 }

 Giving me these results:

 C:\Library\D\src\temp>extime
 1000000 error codes returned in (0.016)
 1000000 exceptions thrown and caught in (7.125)
 1000000 exceptions thrown and caught in (6.969)
 1000000 exceptions thrown and caught in (6.969)
 1000000 exceptions thrown and caught in (7.109)

 In other words approx 7s for exception handling and 16 milliseconds for  
 return codes (likely this equals the time required to call the functions  
 in both cases). Exception handling is then approximately 437 times  
 slower. A single exception took approx 7/1000 of a milliseccond.

 Regan

 On Wed, 29 Jun 2005 05:06:02 +0000 (UTC), AJG <AJG_member pathlink.com>  
 wrote:

 Hi Jarrett (and others too, please do read on),

 Thanks for the info. I was hoping some of the things in the spec were  
 outdated,
 and I still hope it's the case, because exceptions are too powerful to  
 relagate
 to exceptional situations only.

 I think in a way the spec contradicts itself. First, it list all the  
 things it
 wants to eliminate:
 - Returning a NULL pointer.
 - Returning a 0 value.
 - Returning a non-zero error code.
 - Requiring errno to be checked.
 - Requiring that a function be called to check if the previous function  
 failed

 But then it goes on to say what you quoted, which makes it all  
 irrelevant,
 because you are almost never going to encounter these "errors" anyway.

 But really, the question is - why would you design an application to  
 throw
 an exception for anything _other_ than absolutely exceptional  
 circumstances?
 I think it's more of a design issue than anything.

I disagree here. I think errors happen _all the time_, and they _are_ part of the normal program flow. If we use exceptions only as recommended (a very limited subset of errors, the exceptional "catastrophic" ones), we are back to the very "tedious error handling code" we set out to remove in the first place. It's back to the same ol' dirty clutter. Let me give you an example. Suppose I am building an application that requires the user to input a sequence of integers, and it does some processing with them; say, from the command-line. Here it is, with no "handling" in place at all: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # } # } Now, in an ideal world, the user will enter perfectly formatter integers, without a single mistake, and it would all be dandy. But that's not going to happen. And you _know_ that, so you _expect_ an error of this kind. It _is_ part of the regular program flow. Luckily for us, exceptions come into play and save this "quick-and-dirty" utility. If you enter a bunch of letters as an integer, toInt will not choke and segfault. It will throw an exception, which prevents it from going further and damaging anything or producing erroneous output. Since we did not set up any handling (a catch), the program will exit if any input is badly formatted. However, we can take this a step further and make the utility more robust and more useful: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # try { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # catch (Exception e) { # printf("Badly formatted input [#%d]=%.*s\n", index, arg); # printf("Exception says: %.*s\n", e.toString); # } # } # } Now, the program will catch any errors neatly, and continue normal operation (which is perfectly valid). Now ask yourself, how many times will a human being type a number incorrectly? I think we know the answer is _all the time_. What does this mean? We should _use_ the exception mechanism to replace all error handling and checking, but in order to do this, it must be relatively fast. Maybe not as fast as a simple return code, but not an order of magnitude slower either. In this example it doesn't matter much, but what if it's a million inputs? or what if it's a server handling hundreds of thousands of requests? This is all just IMHO, but I think it makes sense. Look at the traditional alternative: You must manually check all inputs beforehand, before even attempting to convert them, to see if they are perfectly formatted. You must introduce an error code return and move the output to its own ref parameter, because the error code could be mistaken for correct output. I think this is futile and redundant; you would be essentially re-writing the toInt function yourself just so that it won't throw an exception. Must one re-invent the wheel? The point of this whole thing: Exceptions are good. They are simple to use and powerful. They make you code neater and reduce useless clutter. Moreover, they represent things that occur commonly. So, we should make use of them whenever possible, and D should make them fast so that we can do this without incurring a penalty. Thanks for listening, --AJG.
 "AJG" <AJG_member pathlink.com> wrote in message
 news:d9s10t$1lid$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching  
 an
 exception in D?

I refer you to the "Error handling" section of D's spec (http://www.digitalmars.com/d/index.html): " Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected. D exception handling fits right in with that. Because errors are unusual, execution of error handling code is not performance critical. Exception handling stack unwinding is a relatively slow process. The normal flow of program logic is performance critical. Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors. " For one, D assumes that exceptions are very unusual circumstances. For two, it says that "error handling code is not performance critical." Now, it won't pause your app for a few seconds when throwing an exception like in .NET, but it won't be _as fast_ as the rest of your code.



Jun 29 2005
next sibling parent reply Sean Kelly <sean f4.ca> writes:
In article <opss4junyl23k2f5 nrage.netwin.co.nz>, Regan Heath says...
I have no idea of the validity of these findings. I really have no idea  
what exactly exception handling does behind the scenes, I was going to  
experiment with adding variables to the stack, and heap in the functions  
to see if it made any difference.

As in most things, there's a size/speed tradeoff in exception handling strategies. I've seen claims on comp.lang.c++.moderated that exception handling can be made almost as fast as returning from a function normally, though most implementations I've used are not quite so fast. I'll try your example in D and in VC++ 2003 on my machine and see what the numbers look like. Sean
Jun 29 2005
parent Sean Kelly <sean f4.ca> writes:
In article <d9uf7e$1ive$1 digitaldaemon.com>, Sean Kelly says...
In article <opss4junyl23k2f5 nrage.netwin.co.nz>, Regan Heath says...
I have no idea of the validity of these findings. I really have no idea  
what exactly exception handling does behind the scenes, I was going to  
experiment with adding variables to the stack, and heap in the functions  
to see if it made any difference.

As in most things, there's a size/speed tradeoff in exception handling strategies. I've seen claims on comp.lang.c++.moderated that exception handling can be made almost as fast as returning from a function normally, though most implementations I've used are not quite so fast. I'll try your example in D and in VC++ 2003 on my machine and see what the numbers look like.

Okay, I rewrote the test code a bit, but the basic idea is the same. Test code is available here: http://home.f4.ca/sean/d/except.d http://home.f4.ca/sean/d/except.cpp http://home.f4.ca/sean/d/except.txt (dump of test results) I tested DMD, DMC, and VC++. Each test had 2 runs to eliminate caching issues. First, I tested a plain non-optimized build, then I tested an optimized build. I tried to have inlining turned off in all tests as it would collapse the deeply nested function calls down to nothing. For VC++ (since I pre-built these exes through the IDE), "standard" is just the "release" template with these changes: Optimization: Disabled Inline Function Expansion: Only __inline "optimized" is also the "release" template, but with these changes: Optimization: Maximize Speed (/O2) Inline Function Expansion: Only __inline I won't vouch for the accuracy of these tests, but from the results it's clear that DM compilers blow the doors off VC++ with the build options as they were. The difference for normal function returns is such that I almost suspect DM of inlining function calls even though I attempted to disable this feature. Also, DMA had far less impact on run time than I expected it to (especially for DM compilers where run time with and without was almost identical). It's also worth noting that VC++ is supposed to have a fairly fast exception handling implementation, but it was slower than DM in pretty much all tests. ########## Digital Mars ########## C:\code\gen\except\dm>test_dm ************************************************************ C:\code\gen\except\dm>dmd -release except.d C:\bin\dmd\bin\..\..\dm\bin\link.exe except,,,user32+kernel32/noi; ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.000000) depth 100: 1000000 iterations completed in (0.010000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (4.593000) depth 50: 1000000 iterations completed in (4.663000) depth 100: 1000000 iterations completed in (4.633000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (5.807000) depth 50: 1000000 iterations completed in (5.876000) depth 100: 1000000 iterations completed in (5.696000) ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.010000) depth 100: 1000000 iterations completed in (0.000000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (4.854000) depth 50: 1000000 iterations completed in (4.834000) depth 100: 1000000 iterations completed in (4.623000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (5.706000) depth 50: 1000000 iterations completed in (5.766000) depth 100: 1000000 iterations completed in (5.606000) ************************************************************ C:\code\gen\except\dm>dmd -release -O except.d C:\bin\dmd\bin\..\..\dm\bin\link.exe except,,,user32+kernel32/noi; ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.010000) depth 100: 1000000 iterations completed in (0.000000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (4.532000) depth 50: 1000000 iterations completed in (4.533000) depth 100: 1000000 iterations completed in (4.553000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (5.596000) depth 50: 1000000 iterations completed in (5.726000) depth 100: 1000000 iterations completed in (5.706000) ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.010000) depth 100: 1000000 iterations completed in (0.010000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (4.503000) depth 50: 1000000 iterations completed in (4.583000) depth 100: 1000000 iterations completed in (4.984000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (5.887000) depth 50: 1000000 iterations completed in (5.655000) depth 100: 1000000 iterations completed in (5.596000) ************************************************************ C:\code\gen\except\dm>dmc -Ae -C except.cpp except.cpp(41) : Warning 18: implied return of throw_ref at closing '}' does not return value except.cpp(55) : Warning 18: implied return of throw_new at closing '}' does not return value except.cpp(103) : Warning 18: implied return of throw_ref at closing '}' does no t return value except.cpp(103) : Warning 18: implied return of throw_new at closing '}' does no t return value link except,,,user32+kernel32/noi; ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.571000) depth 100: 1000000 iterations completed in (1.284000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (6.007000) depth 50: 1000000 iterations completed in (6.037000) depth 100: 1000000 iterations completed in (7.230000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (6.238000) depth 50: 1000000 iterations completed in (6.659000) depth 100: 1000000 iterations completed in (6.669000) ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.000000) depth 50: 1000000 iterations completed in (0.561000) depth 100: 1000000 iterations completed in (1.284000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (6.308000) depth 50: 1000000 iterations completed in (6.458000) depth 100: 1000000 iterations completed in (6.587000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (6.206000) depth 50: 1000000 iterations completed in (6.668000) depth 100: 1000000 iterations completed in (6.647000) ************************************************************ C:\code\gen\except\dm>dmc -Ae -o -C except.cpp except.cpp(41) : Warning 18: implied return of throw_ref at closing '}' does not return value except.cpp(55) : Warning 18: implied return of throw_new at closing '}' does not return value except.cpp(103) : Warning 18: implied return of throw_ref at closing '}' does no t return value except.cpp(103) : Warning 18: implied return of throw_new at closing '}' does no t return value link except,,,user32+kernel32/noi; ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.601000) depth 100: 1000000 iterations completed in (1.384000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (5.735000) depth 50: 1000000 iterations completed in (6.436000) depth 100: 1000000 iterations completed in (6.317000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (6.136000) depth 50: 1000000 iterations completed in (6.657000) depth 100: 1000000 iterations completed in (7.118000) ******************** C:\code\gen\except\dm>except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.622000) depth 100: 1000000 iterations completed in (1.524000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (5.855000) depth 50: 1000000 iterations completed in (6.457000) depth 100: 1000000 iterations completed in (6.316000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (6.226000) depth 50: 1000000 iterations completed in (6.667000) depth 100: 1000000 iterations completed in (6.657000) ########## MSVC++ ########## C:\code\gen\except>test_vc ************************************************************ C:\code\gen\except>standard\except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.732000) depth 100: 1000000 iterations completed in (1.374000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (5.363000) depth 50: 1000000 iterations completed in (5.785000) depth 100: 1000000 iterations completed in (5.966000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (7.078000) depth 50: 1000000 iterations completed in (7.900000) depth 100: 1000000 iterations completed in (7.530000) ******************** C:\code\gen\except>standard\except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.612000) depth 100: 1000000 iterations completed in (1.373000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (5.374000) depth 50: 1000000 iterations completed in (5.755000) depth 100: 1000000 iterations completed in (5.975000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (7.139000) depth 50: 1000000 iterations completed in (7.339000) depth 100: 1000000 iterations completed in (7.449000) ************************************************************ C:\code\gen\except>optimized\except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.010000) depth 100: 1000000 iterations completed in (0.010000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (5.113000) depth 50: 1000000 iterations completed in (5.374000) depth 100: 1000000 iterations completed in (5.705000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (7.258000) depth 50: 1000000 iterations completed in (6.627000) depth 100: 1000000 iterations completed in (6.989000) ******************** C:\code\gen\except>optimized\except ********** testing normal return ********** depth 0: 1000000 iterations completed in (0.010000) depth 50: 1000000 iterations completed in (0.010000) depth 100: 1000000 iterations completed in (0.010000) ********** testing throw ref ********** depth 0: 1000000 iterations completed in (5.093000) depth 50: 1000000 iterations completed in (5.374000) depth 100: 1000000 iterations completed in (5.404000) ********** testing throw new ********** depth 0: 1000000 iterations completed in (6.887000) depth 50: 1000000 iterations completed in (6.668000) depth 100: 1000000 iterations completed in (6.998000)
Jun 29 2005
prev sibling parent reply Dawid =?ISO-8859-2?Q?Ci=EA=BFarkiewicz?= <arael fov.pl> writes:
Regan Heath wrote:

 I have no idea of the validity of these findings. I really have no idea
 what exactly exception handling does behind the scenes, I was going to
 experiment with adding variables to the stack, and heap in the functions
 to see if it made any difference.

There is no point in messuring function that throws exception every time. If you expect Error to happen every time than this no error but return value. Throwing Exception *is* slower - you have to allocate object for exception, unwind stack and such. But errors are not happening every time. Obvoiusly - whole mechanism is called "exceptions" because errors are exceptional. Here goes speed test for more real-like code. import std.stdio; import std.string; import std.c.time; enum { ERROR = 666 } const int ITERATIONS = 30000; time_t sTime, eTime; void printOut(char[] msg, int t){ writef(msg ~ " in ", t, "\n"); } /* we are testing these: */ int foo1Re(){ return 0; } int foo1Ex(){ return 0; } int foo2Re(){ static int foo2ReCounter; if(foo2ReCounter++ == ITERATIONS/4){ foo2ReCounter=0; return ERROR; } return 0; } int foo2Ex(){ static int foo2ExCounter; if(foo2ExCounter++ == ITERATIONS/4){ foo2ExCounter=0; throw new Exception(""); } return 0; } void main() { sTime = time(null); for(int i = 0; i < ITERATIONS; i++) { for(int j = 0; j < ITERATIONS; j++) if(ERROR == foo1Re()){ } } eTime = time(null); printOut("foo1Re",eTime - sTime); sTime = time(null); try { for(int i = 0; i < ITERATIONS; i++) for(int j = 0; j < ITERATIONS; j++) foo1Ex(); } catch (Exception e) { } eTime = time(null); printOut("foo1Ex",eTime - sTime); sTime = time(null); for(int i = 0; i < ITERATIONS*10; i++) { while(ERROR != foo2Re()){}; } eTime = time(null); printOut("foo2Re",eTime - sTime); sTime = time(null); for(int i = 0; i < ITERATIONS*10; i++){ try { while(true) foo2Ex(); } catch (Exception e) { } } eTime = time(null); printOut("foo2Ex",eTime - sTime); } dmd -O -release except.d && ./except gcc except.o -o except -lphobos -lpthread -lm foo1Re in 4 foo1Ex in 4 foo2Re in 13 foo2Ex in 17 As you can see - when there is no throwing - exception can be as fast as returning errorcode. And even when they are thrown code is not much slower. And advantages of using exceptions are realy huge. Resignation from using exception can be justified only by deep analys of profiler output at the end of developing software in execution time critical parts of code. -- Dawid Ciężarkiewicz | arael
Jun 29 2005
parent "Regan Heath" <regan netwin.co.nz> writes:
My intention was to measure the time it took to throw an exception, not to  
compare 2 real-life examples. I already know exception handling is slower,  
I was simply trying to put some numbers down as to how much slower.

IMO exceptions are "fast enough". 7/1000th's of a millisecond is not  
something you're going to notice with the human eye/brain etc.

Regan

On Wed, 29 Jun 2005 18:22:03 +0200, Dawid Ciężarkiewicz <arael fov.pl>  
wrote:

 Regan Heath wrote:

 I have no idea of the validity of these findings. I really have no idea
 what exactly exception handling does behind the scenes, I was going to
 experiment with adding variables to the stack, and heap in the functions
 to see if it made any difference.

There is no point in messuring function that throws exception every time. If you expect Error to happen every time than this no error but return value. Throwing Exception *is* slower - you have to allocate object for exception, unwind stack and such. But errors are not happening every time. Obvoiusly - whole mechanism is called "exceptions" because errors are exceptional. Here goes speed test for more real-like code. import std.stdio; import std.string; import std.c.time; enum { ERROR = 666 } const int ITERATIONS = 30000; time_t sTime, eTime; void printOut(char[] msg, int t){ writef(msg ~ " in ", t, "\n"); } /* we are testing these: */ int foo1Re(){ return 0; } int foo1Ex(){ return 0; } int foo2Re(){ static int foo2ReCounter; if(foo2ReCounter++ == ITERATIONS/4){ foo2ReCounter=0; return ERROR; } return 0; } int foo2Ex(){ static int foo2ExCounter; if(foo2ExCounter++ == ITERATIONS/4){ foo2ExCounter=0; throw new Exception(""); } return 0; } void main() { sTime = time(null); for(int i = 0; i < ITERATIONS; i++) { for(int j = 0; j < ITERATIONS; j++) if(ERROR == foo1Re()){ } } eTime = time(null); printOut("foo1Re",eTime - sTime); sTime = time(null); try { for(int i = 0; i < ITERATIONS; i++) for(int j = 0; j < ITERATIONS; j++) foo1Ex(); } catch (Exception e) { } eTime = time(null); printOut("foo1Ex",eTime - sTime); sTime = time(null); for(int i = 0; i < ITERATIONS*10; i++) { while(ERROR != foo2Re()){}; } eTime = time(null); printOut("foo2Re",eTime - sTime); sTime = time(null); for(int i = 0; i < ITERATIONS*10; i++){ try { while(true) foo2Ex(); } catch (Exception e) { } } eTime = time(null); printOut("foo2Ex",eTime - sTime); } dmd -O -release except.d && ./except gcc except.o -o except -lphobos -lpthread -lm foo1Re in 4 foo1Ex in 4 foo2Re in 13 foo2Ex in 17 As you can see - when there is no throwing - exception can be as fast as returning errorcode. And even when they are thrown code is not much slower. And advantages of using exceptions are realy huge. Resignation from using exception can be justified only by deep analys of profiler output at the end of developing software in execution time critical parts of code.

Jun 29 2005
prev sibling next sibling parent reply Brad Beveridge <brad somewhere.net> writes:
Regan Heath wrote:

 In other words approx 7s for exception handling and 16 milliseconds for  
 return codes (likely this equals the time required to call the 
 functions  in both cases). Exception handling is then approximately 437 
 times slower.  A single exception took approx 7/1000 of a milliseccond.
 
 Regan

From those numbers, assuming that the functions you call do nothing - if you expect to get errors only 1 in 437 runs then it is the same to check for error as it is to throw an exception. Regan, would you mind running those tests again & _NOT_ throwing any exceptions? I'd like to confirm that code that can throw exceptions (but doesn't) is faster in the usual case than checking error codes. I'm from a C background though & checking error code still feels more natural to me :) Brad
Jun 29 2005
parent reply "Regan Heath" <regan netwin.co.nz> writes:
On Wed, 29 Jun 2005 08:20:51 -0700, Brad Beveridge <brad somewhere.net>  
wrote:
 Regan Heath wrote:

 In other words approx 7s for exception handling and 16 milliseconds  
 for  return codes (likely this equals the time required to call the  
 functions  in both cases). Exception handling is then approximately 437  
 times slower.  A single exception took approx 7/1000 of a milliseccond.
  Regan

From those numbers, assuming that the functions you call do nothing - if you expect to get errors only 1 in 437 runs then it is the same to check for error as it is to throw an exception.

I see where you're heading :)
 Regan, would you mind running those tests again & _NOT_ throwing any  
 exceptions?  I'd like to confirm that code that can throw exceptions  
 (but doesn't) is faster in the usual case than checking error codes.

It seems having exceptions there, but not throwing is slightly slower. But still faster than you'll ever notice. C:\Library\D\src\temp>extime 1000000 error codes returned in (0.015) 1000000 exceptions thrown and caught in (0.032) 1000000 exceptions thrown and caught in (0.062) 1000000 exceptions thrown and caught in (0.078) 1000000 exceptions thrown and caught in (0.094) C:\Library\D\src\temp>extime 1000000 error codes returned in (0.015) 1000000 exceptions thrown and caught in (0.063) 1000000 exceptions thrown and caught in (0.062) 1000000 exceptions thrown and caught in (0.079) 1000000 exceptions thrown and caught in (0.078) C:\Library\D\src\temp>extime 1000000 error codes returned in (0.016) 1000000 exceptions thrown and caught in (0.062) 1000000 exceptions thrown and caught in (0.047) <- that's odd? 1000000 exceptions thrown and caught in (0.078) 1000000 exceptions thrown and caught in (0.094)
 I'm from a C background though & checking error code still feels more  
 natural to me :)

Same here. I've been doing a little java recently and things like: RandomAccessFile f = null; try { ..open/use f here.. } catch (IOException ex) { ..handle error.. } finally { try { if (f != null) f.close(); } catch (IOException ex) {} } just seem weird/wrong to me. Maybe I'm just coding this wrong? (advice appreciated) Regan
Jun 29 2005
parent "Regan Heath" <regan netwin.co.nz> writes:
The modified code:

int throws(int i)
{
	if (false) throw new Exception(toString(i));
	return i;
}

int intermediate(int i)
{
	return throws(i);
}

int intermediate2(int i)
{
	return intermediate(i);
}

int intermediate3(int i)
{
	return intermediate2(i);
}

everything else the same.

Regan

On Thu, 30 Jun 2005 10:22:06 +1200, Regan Heath <regan netwin.co.nz> wrote:
 On Wed, 29 Jun 2005 08:20:51 -0700, Brad Beveridge <brad somewhere.net>  
 wrote:
 Regan Heath wrote:

 In other words approx 7s for exception handling and 16 milliseconds  
 for  return codes (likely this equals the time required to call the  
 functions  in both cases). Exception handling is then approximately  
 437 times slower.  A single exception took approx 7/1000 of a  
 milliseccond.
  Regan

From those numbers, assuming that the functions you call do nothing - if you expect to get errors only 1 in 437 runs then it is the same to check for error as it is to throw an exception.

I see where you're heading :)
 Regan, would you mind running those tests again & _NOT_ throwing any  
 exceptions?  I'd like to confirm that code that can throw exceptions  
 (but doesn't) is faster in the usual case than checking error codes.

It seems having exceptions there, but not throwing is slightly slower. But still faster than you'll ever notice. C:\Library\D\src\temp>extime 1000000 error codes returned in (0.015) 1000000 exceptions thrown and caught in (0.032) 1000000 exceptions thrown and caught in (0.062) 1000000 exceptions thrown and caught in (0.078) 1000000 exceptions thrown and caught in (0.094) C:\Library\D\src\temp>extime 1000000 error codes returned in (0.015) 1000000 exceptions thrown and caught in (0.063) 1000000 exceptions thrown and caught in (0.062) 1000000 exceptions thrown and caught in (0.079) 1000000 exceptions thrown and caught in (0.078) C:\Library\D\src\temp>extime 1000000 error codes returned in (0.016) 1000000 exceptions thrown and caught in (0.062) 1000000 exceptions thrown and caught in (0.047) <- that's odd? 1000000 exceptions thrown and caught in (0.078) 1000000 exceptions thrown and caught in (0.094)
 I'm from a C background though & checking error code still feels more  
 natural to me :)

Same here. I've been doing a little java recently and things like: RandomAccessFile f = null; try { ..open/use f here.. } catch (IOException ex) { ..handle error.. } finally { try { if (f != null) f.close(); } catch (IOException ex) {} } just seem weird/wrong to me. Maybe I'm just coding this wrong? (advice appreciated) Regan

Jun 29 2005
prev sibling parent reply Nicolas Lehuen <Nicolas_member pathlink.com> writes:
Are you sure you're not measuring the performance of new Exception(toString(i))
here ? Especially toString(i) ? I don't think your benchmark is very fair,
because in one case you are purely stack based, and in the other you are
allocating tons of objects on the heap.

Regards,
Nicolas

In article <opss4jgrgc23k2f5 nrage.netwin.co.nz>, Regan Heath says...
Ok, so I figured why not try and time the exception handling. Here is what  
I came up with:

import std.stdio;
import std.string;
import std.c.time;
extern(C)
{
	struct timeb
	{
		time_t time;
		ushort millitm;
		short timezone, dstflag;
	}

	void _ftime(timeb *);
	alias _ftime ftime;
}

double diff(timeb* start, timeb* finish)
{
	double d;
	
	d =  finish.time-start.time;
	d += (cast(double)(finish.millitm-start.millitm))/1000.0;
	return d;
}

const int ITERATIONS = 1000000;

void throws(int i)
{
	throw new Exception(toString(i));
}

void intermediate(int i)
{
	throws(i);
}

void intermediate2(int i)
{
	intermediate(i);
}

void intermediate3(int i)
{
	intermediate2(i);
}

int returns(int i)
{
	return i;
}

void main()
{
	timeb start;
	timeb finish;
	int r;
	
	ftime(&start);	
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			r = returns(i);
		}
		catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," error codes returned in  
(",diff(&start,&finish),")");
	
	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			throws(i);
		}
		catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");
	
	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			intermediate(i);
		}
			catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");

	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			intermediate2(i);
		}
			catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");

	ftime(&start);
	for(int i = 0; i < ITERATIONS; i++) {
		try {
			intermediate3(i);
		}
			catch (Exception e) {
		}
	}
	ftime(&finish);
	writefln(ITERATIONS," exceptions thrown and caught in  
(",diff(&start,&finish),")");
}

Giving me these results:

C:\Library\D\src\temp>extime
1000000 error codes returned in (0.016)
1000000 exceptions thrown and caught in (7.125)
1000000 exceptions thrown and caught in (6.969)
1000000 exceptions thrown and caught in (6.969)
1000000 exceptions thrown and caught in (7.109)

In other words approx 7s for exception handling and 16 milliseconds for  
return codes (likely this equals the time required to call the functions  
in both cases). Exception handling is then approximately 437 times slower.  
A single exception took approx 7/1000 of a milliseccond.

Regan

On Wed, 29 Jun 2005 05:06:02 +0000 (UTC), AJG <AJG_member pathlink.com>  
wrote:

 Hi Jarrett (and others too, please do read on),

 Thanks for the info. I was hoping some of the things in the spec were  
 outdated,
 and I still hope it's the case, because exceptions are too powerful to  
 relagate
 to exceptional situations only.

 I think in a way the spec contradicts itself. First, it list all the  
 things it
 wants to eliminate:
 - Returning a NULL pointer.
 - Returning a 0 value.
 - Returning a non-zero error code.
 - Requiring errno to be checked.
 - Requiring that a function be called to check if the previous function  
 failed

 But then it goes on to say what you quoted, which makes it all  
 irrelevant,
 because you are almost never going to encounter these "errors" anyway.

 But really, the question is - why would you design an application to  
 throw
 an exception for anything _other_ than absolutely exceptional  
 circumstances?
 I think it's more of a design issue than anything.

I disagree here. I think errors happen _all the time_, and they _are_ part of the normal program flow. If we use exceptions only as recommended (a very limited subset of errors, the exceptional "catastrophic" ones), we are back to the very "tedious error handling code" we set out to remove in the first place. It's back to the same ol' dirty clutter. Let me give you an example. Suppose I am building an application that requires the user to input a sequence of integers, and it does some processing with them; say, from the command-line. Here it is, with no "handling" in place at all: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # } # } Now, in an ideal world, the user will enter perfectly formatter integers, without a single mistake, and it would all be dandy. But that's not going to happen. And you _know_ that, so you _expect_ an error of this kind. It _is_ part of the regular program flow. Luckily for us, exceptions come into play and save this "quick-and-dirty" utility. If you enter a bunch of letters as an integer, toInt will not choke and segfault. It will throw an exception, which prevents it from going further and damaging anything or producing erroneous output. Since we did not set up any handling (a catch), the program will exit if any input is badly formatted. However, we can take this a step further and make the utility more robust and more useful: # import std.conv; # alias char[] string; # # int processInt(int input) { # int output; # // Processing. # return (output); # } # # main(string[] args) { # foreach (int index, string arg; args[1 .. length]) { # try { # int temp = toInt(arg); # int result = processInt(temp); # printf("[#%d] %d => %d\n", index, temp, result); # catch (Exception e) { # printf("Badly formatted input [#%d]=%.*s\n", index, arg); # printf("Exception says: %.*s\n", e.toString); # } # } # } Now, the program will catch any errors neatly, and continue normal operation (which is perfectly valid). Now ask yourself, how many times will a human being type a number incorrectly? I think we know the answer is _all the time_. What does this mean? We should _use_ the exception mechanism to replace all error handling and checking, but in order to do this, it must be relatively fast. Maybe not as fast as a simple return code, but not an order of magnitude slower either. In this example it doesn't matter much, but what if it's a million inputs? or what if it's a server handling hundreds of thousands of requests? This is all just IMHO, but I think it makes sense. Look at the traditional alternative: You must manually check all inputs beforehand, before even attempting to convert them, to see if they are perfectly formatted. You must introduce an error code return and move the output to its own ref parameter, because the error code could be mistaken for correct output. I think this is futile and redundant; you would be essentially re-writing the toInt function yourself just so that it won't throw an exception. Must one re-invent the wheel? The point of this whole thing: Exceptions are good. They are simple to use and powerful. They make you code neater and reduce useless clutter. Moreover, they represent things that occur commonly. So, we should make use of them whenever possible, and D should make them fast so that we can do this without incurring a penalty. Thanks for listening, --AJG.
 "AJG" <AJG_member pathlink.com> wrote in message
 news:d9s10t$1lid$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching  
 an
 exception in D?

I refer you to the "Error handling" section of D's spec (http://www.digitalmars.com/d/index.html): " Errors are not part of the normal flow of a program. Errors are exceptional, unusual, and unexpected. D exception handling fits right in with that. Because errors are unusual, execution of error handling code is not performance critical. Exception handling stack unwinding is a relatively slow process. The normal flow of program logic is performance critical. Since the normal flow code does not have to check every function call for error returns, it can be realistically faster to use exception handling for the errors. " For one, D assumes that exceptions are very unusual circumstances. For two, it says that "error handling code is not performance critical." Now, it won't pause your app for a few seconds when throwing an exception like in .NET, but it won't be _as fast_ as the rest of your code.



Jun 30 2005
parent Sean Kelly <sean f4.ca> writes:
In article <da0ueg$15ls$1 digitaldaemon.com>, Nicolas Lehuen says...
Are you sure you're not measuring the performance of new Exception(toString(i))
here ? Especially toString(i) ? I don't think your benchmark is very fair,
because in one case you are purely stack based, and in the other you are
allocating tons of objects on the heap.

See my post on exception performance. I modified the test so it measures both stack-based and dynamic exception allocation. FWIW, the times were almost identical. Sean
Jun 30 2005
prev sibling next sibling parent reply "Walter" <newshound digitalmars.com> writes:
"AJG" <AJG_member pathlink.com> wrote in message
news:d9s10t$1lid$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D? First, why do I ask this:

D's exception handling mechanism is the same as C++'s is, as D and C++ share the same back end. It'll still be an order or magnitude or more slower than, say, returning an error code.
Jun 28 2005
next sibling parent reply AJG <AJG_member pathlink.com> writes:
Hi Walter,

 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D? First, why do I ask this:

D's exception handling mechanism is the same as C++'s is, as D and C++ share the same back end. It'll still be an order or magnitude or more slower than, say, returning an error code.

Thanks for the info. I was wondering whether this has to be the case. - Is there a way to make exceptions somewhat lighter (and faster)? - Will D always share the C++ backend for exceptions, or is it at least theoretically possible to eventually substitute it with a more performant version? - What about making the Error class a full-blown stack unwind with stack trace and everything (i.e. slow), and the Exception class something lighter akin to an error code (i.e. fast)? I think this would be _immensely_ powerful and useful; it would be a clean, simple way to unify all error handling into one framework. Thanks for listening, --AJG. PS: I will post an example re: exceptions in a second, could you take a look-see? ================================ 2B || !2B, that is the question.
Jun 28 2005
parent "Walter" <newshound digitalmars.com> writes:
"AJG" <AJG_member pathlink.com> wrote in message
news:d9t6a2$2n1h$1 digitaldaemon.com...
 What is the cost (performance & memory-wise) of throwing and catching



 exception in D? First, why do I ask this:

D's exception handling mechanism is the same as C++'s is, as D and C++


the same back end. It'll still be an order or magnitude or more slower


say, returning an error code.

Thanks for the info. I was wondering whether this has to be the case. - Is there a way to make exceptions somewhat lighter (and faster)?

I won't say no, but a lot of engineers have looked at the way C++ exceptions are done, and if there was an easier way then someone probably would have implemented it by now.
 - Will D always share the C++ backend for exceptions, or is it at least
 theoretically possible to eventually substitute it with a more performant
 version?

It's possible, but it would have to be a big improvement to justify it.
 - What about making the Error class a full-blown stack unwind with stack

 and everything (i.e. slow), and the Exception class something lighter akin

 error code (i.e. fast)? I think this would be _immensely_ powerful and

 it would be a clean, simple way to unify all error handling into one

I'm reluctant to have two different exception schemes active in the compiler. It took me long enough to understand and debug the one!
 Thanks for listening,
 --AJG.

 PS: I will post an example re: exceptions in a second, could you take a
 look-see?




 ================================
 2B || !2B, that is the question.

Jun 28 2005
prev sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Walter wrote:
 "AJG" <AJG_member pathlink.com> wrote in message
 news:d9s10t$1lid$1 digitaldaemon.com...
 
 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D? First, why do I ask this:

D's exception handling mechanism is the same as C++'s is, as D and C++ share the same back end. It'll still be an order or magnitude or more slower than, say, returning an error code.

But is it true that this slowdown happens only when an exception is actually thrown? So the normal flow of logic would be faster as it doesn't have to keep checking for errors. If no exceptions are thrown, then do try/catch/finally blocks slow the program down at all? Stewart. -- My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Jul 01 2005
parent Sean Kelly <sean f4.ca> writes:
In article <da3kfi$19m2$1 digitaldaemon.com>, Stewart Gordon says...
Walter wrote:
 "AJG" <AJG_member pathlink.com> wrote in message
 news:d9s10t$1lid$1 digitaldaemon.com...
 
 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D? First, why do I ask this:

D's exception handling mechanism is the same as C++'s is, as D and C++ share the same back end. It'll still be an order or magnitude or more slower than, say, returning an error code.

But is it true that this slowdown happens only when an exception is actually thrown? So the normal flow of logic would be faster as it doesn't have to keep checking for errors. If no exceptions are thrown, then do try/catch/finally blocks slow the program down at all?

They can, depending on the implementation. But there is no theoretical reason why they have to. I haven't done any testing in this regard, but I have a feeling there is negligible overhead in putting try blocks in your code. Sean
Jul 01 2005
prev sibling parent reply =?iso-8859-1?q?Knud_S=F8rensen?= <12tkvvb02 sneakemail.com> writes:
According to the shootout the answer is NO!

http://shootout.alioth.debian.org/sandbox/benchmark.php?test=except&lang=all&sort=fullcpu

D use 10 times as much time as the fastest language 
on the exception test.



On Tue, 28 Jun 2005 17:22:37 +0000, AJG wrote:

 Hi there,
 
 What is the cost (performance & memory-wise) of throwing and catching an
 exception in D? First, why do I ask this:
 
 1) Exceptions are actually a great way to handle errors.
 2) Thus, it would be great to handle ALL errors via exceptions.
 Gets rid of return codes, errnos, null-checking, etc.
 3) However, error-handling MUST be fast and efficient.
 Particularly errors that you expect will happen relatively commonly.
 4) Hence, none of this will work if exceptions are slow.
 
 Why I'm I concerned?
 
 Well, in C#, which is pretty cool (but managed) and has structured exception
 handling, there is one nasty restriction:
 
 Exceptions are _clearly_ only meant for "exceptional" situations. Things that
 shouldn't happen a lot: running out of memory, database connection failed, etc.
 
 This restriction is due to [a] various authors recommending such design;
 understandable, given [b] the fact that when an exception is thrown, your
 program halts execution literally for a couple of _human_ seconds.
 
 This is simply unacceptable for a simple thing like an "invalid input string."
 So in C#, we are forced to do "efficient" error checking _before_ a potential
 exception is thrown. In essence, exceptions in C# are a last resort.
 
 So, to recap, is it possible to handle all errors in D via exceptions (without
a
 speed cost), or should the C# approach be taken: using exceptions only for
 exceptional stuff (a limitation, IMHO)?
 
 Thanks!
 --AJG.
 
 2B || !2B, that is the question.
 ================================

Jun 29 2005
parent reply David Medlock <noone nowhere.com> writes:
Knud Sřrensen wrote:
 According to the shootout the answer is NO!
 
 http://shootout.alioth.debian.org/sandbox/benchmark.php?test=except&lang=all&sort=fullcpu
 
 D use 10 times as much time as the fastest language 
 on the exception test.
 
 
 
 On Tue, 28 Jun 2005 17:22:37 +0000, AJG wrote:
 
 
Hi there,

What is the cost (performance & memory-wise) of throwing and catching an
exception in D? First, why do I ask this:

1) Exceptions are actually a great way to handle errors.
2) Thus, it would be great to handle ALL errors via exceptions.
Gets rid of return codes, errnos, null-checking, etc.
3) However, error-handling MUST be fast and efficient.
Particularly errors that you expect will happen relatively commonly.
4) Hence, none of this will work if exceptions are slow.

Why I'm I concerned?

Well, in C#, which is pretty cool (but managed) and has structured exception
handling, there is one nasty restriction:

Exceptions are _clearly_ only meant for "exceptional" situations. Things that
shouldn't happen a lot: running out of memory, database connection failed, etc.

This restriction is due to [a] various authors recommending such design;
understandable, given [b] the fact that when an exception is thrown, your
program halts execution literally for a couple of _human_ seconds.

This is simply unacceptable for a simple thing like an "invalid input string."
So in C#, we are forced to do "efficient" error checking _before_ a potential
exception is thrown. In essence, exceptions in C# are a last resort.

So, to recap, is it possible to handle all errors in D via exceptions (without a
speed cost), or should the C# approach be taken: using exceptions only for
exceptional stuff (a limitation, IMHO)?

Thanks!
--AJG.

2B || !2B, that is the question.
================================


Compare apples to apples. D is 10 times FASTER than the intel C++ compiler, which is widely reguarded as the best optimizing C++ compiler. All the languages above it are using a different programming paradigm entirely, functional programming. The C examples are laughable, as they are basically using int error codes and setjmp/longjmp. -DavidM
Jun 29 2005
parent Dave <Dave_member pathlink.com> writes:
In article <d9ufp5$1jh4$1 digitaldaemon.com>, David Medlock says...
Knud Sřrensen wrote:
 According to the shootout the answer is NO!
 
 http://shootout.alioth.debian.org/sandbox/benchmark.php?test=except&lang=all&sort=fullcpu
 
 D use 10 times as much time as the fastest language 
 on the exception test.
 

Compare apples to apples. D is 10 times FASTER than the intel C++ compiler, which is widely reguarded as the best optimizing C++ compiler. All the languages above it are using a different programming paradigm entirely, functional programming. The C examples are laughable, as they are basically using int error codes and setjmp/longjmp.

Yep - and if you use a base class for the exceptions with a free list and overloaded new and delete, you can get code just as fast as the fastest on that list (use the following code at your own risk - I haven't fully tested it), which should make throwing exceptions repeatedly in a tight loop about as fast as it gets in any language. And more to the OP's question, even w/o overloading new and delete, D is still the fastest by a wide margin using imperitive language try/catch EH (compare it to mono C# - D is several times faster in the list above). And this will only improve as the D GC improves, getting rid of the need for custom mem. mgmt. On my machine, the time for 250K iterations went from 0.250 to 0.09 using the free list approach. <code> class ExMem { private: static ExMem __emList; ExMem __emNext; protected: new(uint sz) { void* p; if(__emList) { p = cast(void*)__emList; __emList = __emList.__emNext; } else { p = new void[sz]; } return p; } delete(void* p) { if(p) { ExMem e = cast(ExMem)p; e.__emNext = __emList; __emList = e; } } } class Hi_exception : ExMem { public: this(size_t _n) { n = _n; } char[] what() { return(std.string.toString(n)); } private: size_t n; } class Lo_exception : ExMem { public: this(size_t _n) { n = _n; } char[] what() { return(std.string.toString(n)); } private: size_t n; char N[8]; } .. void lo_function(size_t num) { try { blowup(num); } catch(Lo_exception ex) { ++LO; delete ex; // Back into free-list } } void hi_function(size_t num) { try { lo_function(num); } catch(Hi_exception ex) { ++HI; delete ex; // Back into free-list } } </code>
Jul 01 2005