June 29, 2005
"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
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)?

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
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.

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.


June 29, 2005
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.
>
>

June 29, 2005
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.
>>
>>
>

June 29, 2005
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. ================================

June 29, 2005
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
June 29, 2005
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


June 29, 2005
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
June 29, 2005
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
June 29, 2005
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)


June 29, 2005
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.