March 30, 2011
On 03/29/2011 09:32 PM, Ali Çehreli wrote:
> On 03/29/2011 03:40 PM, Kai Meyer wrote:
>
>  > I was given two words of advice on exceptions:
>  > "Use exceptions for the exceptional"
>  > "Use exceptions only for the exceptional"
>
> Those advices are given by wise people: they are wise only because they
> leave the definition as vague as "exceptional." :)
>

Ya, now that I'm thinking about it a little more, we were talking about what sort of things you can do to increase performance. This was actually pretty far down the list, after considering things like profiling, improving algorithms, using built-in types instead of classes, ect. Exceptions are a little more expensive than condition statements.

> And what do we do for the "not so exceptional"? Do we return error
> codes? So the function implementation will be complicated and the caller
> code will be complicated.

You're right. I would consider those complications only if the optimisation I'm after justifies them. For example, if our profiler indicates that the function is running slowly due to handling a lot of exceptions (which probably won't be the first thing the profiler finds is running slow), we could use conditional statements to speed things up a bit.

if (good data)
  do work
else
  report "can't do work"

Would be faster than:

try
  do work
catch
  report "can't do work"

I'll also add that the same person who gave me this advice also likes to say "Premature optimisation is the root of all evil."

>
> Exceptions are a great tool to eliminate the need for error codes.
>
> Here is what I follow:
>
> - Functions have specific tasks to do; if those tasks cannot be
> accomplished, the function must throw.
>
> In some cases the function can continue, but that behavior must be
> documented. For example, if an HTML library function is responsible for
> making HTML headers, of which only the levels in the range of 1-6 are
> valid, that function may throw when the level is outside of the valid
> range, for in that case it cannot "make an HTML header"; or it can
> document that if the level is outside of the range, 1 or 6 will be used.
>
> - Catch exceptions only when there is a sensible thing to do at that
> level: log an error, skip that operation, go back to the user with an
> error code, take corrective action, etc.
>
> Disclaimer: That is what I follow in C++ code. I don't have experience
> with exception safety in D. I don't know issues that may be specific to D.
>
> Ali
>


Thanks for your insight Ali :) I'm not sure D's exceptions are much different than C++'s. I think you're right on.
March 30, 2011
On 03/30/2011 08:42 AM, Kai Meyer wrote:

> we were talking about what sort of things you can do to
> increase performance

Sorry to change the topic. :) I am fortunate that what I currently work on does not require more than being careful about not using the wrong algorithms and data structures.

> if (good data)
> do work
> else
> report "can't do work"

That is helpful as long as the data can be checked up front, but we can't always be sure:

if (file_exists()) {
    use_file();   // <-- the file may not exist

or

if (server_is_up()) {
    talk_to_server();  // <-- the server may not be up

> Would be faster than:
>
> try
> do work
> catch
> report "can't do work"

Apparently that's what Denis' (spir) measurements show as well and that's very unfortunate. I would like to put that slowness under the "quality of implementation" category. Although, it's well known that exception handling has been slow (e.g. in C++) in general too.

(Note: I heard that scope(failure) is lowered to a try-catch block by the compiler; so it should be slow too.)

If exceptions are inherently slow, that must be because they provide more than what we compare them against. A good example is comparing old features of C to C++. Some people claim that calling virtual functions is slow due to jumping off the vtbl. Correct, but let's not forget that achieving the same in C would be slow too.

Your examples do have such a feature difference: the code that uses the try-catch block will always execute the code in the catch clause. On the other hand, the code that checks the data before hand may not report "can't do work", as "do work" may get complicated in the future and may call a function that may throw directly or indirectly. And suddenly the function doesn't work anymore and the changes that we've made are detached from this function. Bad bug! :)

We may argue that exceptions should not exist in D (or C++) but they are so helpful (hey, I know we all know these :)):

- exceptions make it impossible (or very hard) to continue with bad program state

- they allow functions to return objects by freeing the return value from always being an error code (return values are preferable to side effects)

- they eliminate boiler plate error management lines (To complicate matters, when a function needs to do cleanup after an error, it may call other functions that may return more error codes. We must be careful not to change the value of the original error code variable.)

- they result in less lines of code (bugs live in lines of code :))

I would like to show two functions written in C and C++. To my knowledge, they are well written and don't have any resource leaks:

// a C function

int bar_C(Resource ** in_out)
{
    int err = 0;

    Resource * r0 = NULL;
    Resource * r1 = NULL;

    err = allocate_resource(&r0);
    if (err) goto finally;

    err = allocate_resource(&r1);
    if (err) goto finally;

    /* ... use r0 and r1 here ...  */

    if (err) goto finally;

    /* transfer ownership */
    *in_out = r0;
    r0 = NULL;

finally:

    deallocate_resource(&r1);
    deallocate_resource(&r0);
    return err;
}

// The equivalent C++ function

Resource bar_CPP()
{
    Resource r0(/* ... */);
    Resource r1(/* ... */);

    /* ... use r0 and r1 here ... */

    /* transfer ownership */
    return r0;
}

Of course the latter takes advantage of other features of C++, but it also shows that there is no explicit error management when the code relies on exceptions. bar_CPP() just does what it is supposed to do. Yes, there may be errors but bar_CPP() doesn't care. And the callers may or may not catch the errors, but bar_CPP() doesn't care.

> I'm not sure D's exceptions are much different than C++'s.

Yeah, it must be the same as what Digital Mars C++ compiler uses. (Except, D also has the 'finally' clause.)

In summary: I hope I will never go back to pass-the-error-code style of coding.

Ali

March 30, 2011
On 2011-03-30 05:09, spir wrote:
> On 03/30/2011 05:32 AM, Ali Çehreli wrote:
> > On 03/29/2011 03:40 PM, Kai Meyer wrote:
> >>  I was given two words of advice on exceptions:
> >>  "Use exceptions for the exceptional"
> >>  "Use exceptions only for the exceptional"
> > 
> > Those advices are given by wise people: they are wise only because they leave the definition as vague as "exceptional." :)
> > 
> > And what do we do for the "not so exceptional"? Do we return error codes? So the function implementation will be complicated and the caller code will be complicated.
> > 
> > Exceptions are a great tool to eliminate the need for error codes.
> > 
> > Here is what I follow:
> > 
> > - Functions have specific tasks to do; if those tasks cannot be accomplished, the function must throw.
> > 
> > In some cases the function can continue, but that behavior must be documented. For example, if an HTML library function is responsible for making HTML headers, of which only the levels in the range of 1-6 are valid, that function may throw when the level is outside of the valid range, for in that case it cannot "make an HTML header"; or it can document that if the level is outside of the range, 1 or 6 will be used.
> > 
> > - Catch exceptions only when there is a sensible thing to do at that level: log an error, skip that operation, go back to the user with an error code, take corrective action, etc.
> > 
> > Disclaimer: That is what I follow in C++ code. I don't have experience with exception safety in D. I don't know issues that may be specific to D.
> 
> These are sensible and well expressed guidelines, thank you.
> In other languages, I happened to use exceptions as a // channel for
> side-information (eg 'nomatch' for a matching func), but in D I realised
> how 'exceptionnally' (!) costly throwing & catching exceptions is, so that
> I do not do it anymore. No idea though whether this exceptional cost is
> perticular to D.

I'd have to measure it in C++, Java, and C#, but I'm pretty sure that D's is at least a lot slower than Java. Java uses exceptions all over the place, and it works quite well overall IMHO, but I never got the impression that there was any kind of major overhead for exceptions (though obviously there's going to be at least _some_ performance hit for derailing the thread of execution like that). In D however, it seems to be significant. As I've been reworking std.datetime's unit tests, I've found that I've had to be very careful about how often I use assertThrown, or there is a _major_ increase in how long the unit tests take to execute. While changing some tests, I had some loops which included assertThrown. It turns out that with assertThrown, they'd be 10+ seconds long, whereas without, they'd be a matter of milliseconds. So, the performance of exceptions in D is quite poor and while it probably doesn't matter all that much for normal code execution, it's definitely annoying for heavy unit testing which is validating that functions throw when they're supposed to throw.

As a test, on my machine, this program

===
import std.datetime;
import std.exception;
import std.stdio;

void main()
{
    auto date = Date(2011, 5, 7);
    {
        auto curr = Clock.currTime();
        assertNotThown!DateTimeException(date.day = 29);
        writeln(curr - Clock.currTime());
    }

    {
        auto curr = Clock.currTime();
        assertThrown!DateTimeException(date.day = 32);
        writeln(curr - Clock.currTime());
    }
}
===

prints out this

-1 μs and -4 hnsecs
-637 μs and -7 hnsecs

Naturally, the executation time does vary some, but it's consistently over 400 times (and generally more like 450 times) more expensive to have the exception be thrown and caught than it is to have it not be thrown. That's _really_ expensive. I'd be stunned if Java's or C#'s were that bad, but I'd have to go and test it. C++'s might be, but given how much Java and C# use exceptions, it would ludicrous if either of them had that kind of overhead for exceptions.

- Jonathan M Davis
March 30, 2011
On 03/30/2011 12:40 PM, Jonathan M Davis wrote:
> On 2011-03-30 05:09, spir wrote:
>> On 03/30/2011 05:32 AM, Ali Çehreli wrote:
>>> On 03/29/2011 03:40 PM, Kai Meyer wrote:
>>>>   I was given two words of advice on exceptions:
>>>>   "Use exceptions for the exceptional"
>>>>   "Use exceptions only for the exceptional"
>>>
>>> Those advices are given by wise people: they are wise only because they
>>> leave the definition as vague as "exceptional." :)
>>>
>>> And what do we do for the "not so exceptional"? Do we return error codes?
>>> So the function implementation will be complicated and the caller code
>>> will be complicated.
>>>
>>> Exceptions are a great tool to eliminate the need for error codes.
>>>
>>> Here is what I follow:
>>>
>>> - Functions have specific tasks to do; if those tasks cannot be
>>> accomplished, the function must throw.
>>>
>>> In some cases the function can continue, but that behavior must be
>>> documented. For example, if an HTML library function is responsible for
>>> making HTML headers, of which only the levels in the range of 1-6 are
>>> valid, that function may throw when the level is outside of the valid
>>> range, for in that case it cannot "make an HTML header"; or it can
>>> document that if the level is outside of the range, 1 or 6 will be used.
>>>
>>> - Catch exceptions only when there is a sensible thing to do at that
>>> level: log an error, skip that operation, go back to the user with an
>>> error code, take corrective action, etc.
>>>
>>> Disclaimer: That is what I follow in C++ code. I don't have experience
>>> with exception safety in D. I don't know issues that may be specific to
>>> D.
>>
>> These are sensible and well expressed guidelines, thank you.
>> In other languages, I happened to use exceptions as a // channel for
>> side-information (eg 'nomatch' for a matching func), but in D I realised
>> how 'exceptionnally' (!) costly throwing&  catching exceptions is, so that
>> I do not do it anymore. No idea though whether this exceptional cost is
>> perticular to D.
>
> I'd have to measure it in C++, Java, and C#, but I'm pretty sure that D's is
> at least a lot slower than Java. Java uses exceptions all over the place, and
> it works quite well overall IMHO, but I never got the impression that there
> was any kind of major overhead for exceptions (though obviously there's going
> to be at least _some_ performance hit for derailing the thread of execution
> like that). In D however, it seems to be significant. As I've been reworking
> std.datetime's unit tests, I've found that I've had to be very careful about
> how often I use assertThrown, or there is a _major_ increase in how long the
> unit tests take to execute. While changing some tests, I had some loops which
> included assertThrown. It turns out that with assertThrown, they'd be 10+
> seconds long, whereas without, they'd be a matter of milliseconds. So, the
> performance of exceptions in D is quite poor and while it probably doesn't
> matter all that much for normal code execution, it's definitely annoying for
> heavy unit testing which is validating that functions throw when they're
> supposed to throw.
>
> As a test, on my machine, this program
>
> ===
> import std.datetime;
> import std.exception;
> import std.stdio;
>
> void main()
> {
>      auto date = Date(2011, 5, 7);
>      {
>          auto curr = Clock.currTime();
>          assertNotThown!DateTimeException(date.day = 29);
>          writeln(curr - Clock.currTime());
>      }
>
>      {
>          auto curr = Clock.currTime();
>          assertThrown!DateTimeException(date.day = 32);
>          writeln(curr - Clock.currTime());
>      }
> }
> ===
>
> prints out this
>
> -1 μs and -4 hnsecs
> -637 μs and -7 hnsecs

That's too much. :) I get consistent results with -O on a 64-bit Ubuntu:

-1 μs and -9 hnsecs
-832 μs and -9 hnsecs

But with the addition of the -m64 flag, it's more than 4 times faster:

-1 μs
-175 μs and -4 hnsecs

Still not good. :-/

Ali

>
> Naturally, the executation time does vary some, but it's consistently over 400
> times (and generally more like 450 times) more expensive to have the exception
> be thrown and caught than it is to have it not be thrown. That's _really_
> expensive. I'd be stunned if Java's or C#'s were that bad, but I'd have to go
> and test it. C++'s might be, but given how much Java and C# use exceptions, it
> would ludicrous if either of them had that kind of overhead for exceptions.
>
> - Jonathan M Davis

March 30, 2011
On 2011-03-30 14:05, Ali Çehreli wrote:
> On 03/30/2011 12:40 PM, Jonathan M Davis wrote:
> > On 2011-03-30 05:09, spir wrote:
> >> On 03/30/2011 05:32 AM, Ali Çehreli wrote:
> >>> On 03/29/2011 03:40 PM, Kai Meyer wrote:
> >>>>   I was given two words of advice on exceptions:
> >>>>   "Use exceptions for the exceptional"
> >>>>   "Use exceptions only for the exceptional"
> >>> 
> >>> Those advices are given by wise people: they are wise only because they leave the definition as vague as "exceptional." :)
> >>> 
> >>> And what do we do for the "not so exceptional"? Do we return error codes? So the function implementation will be complicated and the caller code will be complicated.
> >>> 
> >>> Exceptions are a great tool to eliminate the need for error codes.
> >>> 
> >>> Here is what I follow:
> >>> 
> >>> - Functions have specific tasks to do; if those tasks cannot be accomplished, the function must throw.
> >>> 
> >>> In some cases the function can continue, but that behavior must be documented. For example, if an HTML library function is responsible for making HTML headers, of which only the levels in the range of 1-6 are valid, that function may throw when the level is outside of the valid range, for in that case it cannot "make an HTML header"; or it can document that if the level is outside of the range, 1 or 6 will be used.
> >>> 
> >>> - Catch exceptions only when there is a sensible thing to do at that level: log an error, skip that operation, go back to the user with an error code, take corrective action, etc.
> >>> 
> >>> Disclaimer: That is what I follow in C++ code. I don't have experience with exception safety in D. I don't know issues that may be specific to D.
> >> 
> >> These are sensible and well expressed guidelines, thank you.
> >> In other languages, I happened to use exceptions as a // channel for
> >> side-information (eg 'nomatch' for a matching func), but in D I realised
> >> how 'exceptionnally' (!) costly throwing&  catching exceptions is, so
> >> that I do not do it anymore. No idea though whether this exceptional
> >> cost is perticular to D.
> > 
> > I'd have to measure it in C++, Java, and C#, but I'm pretty sure that D's is at least a lot slower than Java. Java uses exceptions all over the place, and it works quite well overall IMHO, but I never got the impression that there was any kind of major overhead for exceptions (though obviously there's going to be at least _some_ performance hit for derailing the thread of execution like that). In D however, it seems to be significant. As I've been reworking std.datetime's unit tests, I've found that I've had to be very careful about how often I use assertThrown, or there is a _major_ increase in how long the unit tests take to execute. While changing some tests, I had some loops which included assertThrown. It turns out that with assertThrown, they'd be 10+ seconds long, whereas without, they'd be a matter of milliseconds. So, the performance of exceptions in D is quite poor and while it probably doesn't matter all that much for normal code execution, it's definitely annoying for heavy unit testing which is validating that functions throw when they're supposed to throw.
> > 
> > As a test, on my machine, this program
> > 
> > ===
> > import std.datetime;
> > import std.exception;
> > import std.stdio;
> > 
> > void main()
> > {
> > 
> >      auto date = Date(2011, 5, 7);
> >      {
> > 
> >          auto curr = Clock.currTime();
> >          assertNotThown!DateTimeException(date.day = 29);
> >          writeln(curr - Clock.currTime());
> > 
> >      }
> > 
> >      {
> > 
> >          auto curr = Clock.currTime();
> >          assertThrown!DateTimeException(date.day = 32);
> >          writeln(curr - Clock.currTime());
> > 
> >      }
> > 
> > }
> > ===
> > 
> > prints out this
> > 
> > -1 μs and -4 hnsecs
> > -637 μs and -7 hnsecs
> 
> That's too much. :) I get consistent results with -O on a 64-bit Ubuntu:
> 
> -1 μs and -9 hnsecs
> -832 μs and -9 hnsecs
> 
> But with the addition of the -m64 flag, it's more than 4 times faster:
> 
> -1 μs
> -175 μs and -4 hnsecs
> 
> Still not good. :-/

LOL. And I just realized that I did those subtractions backwards. They're negative where they should be positive. Oh well. The numbers are still clear, and I was in a hurry when I wrote the test code.

- Jonathan M Davis
March 30, 2011
Jonathan M Davis:

> Naturally, the executation time does vary some, but it's consistently over 400 times (and generally more like 450 times) more expensive to have the exception be thrown and caught than it is to have it not be thrown.

On Windows in a benchmark I've seen thrown exceptions about as 12 times slower than Java ones.

Bye,
bearophile
March 31, 2011
Kai Meyer Wrote:

> On 03/30/2011 08:25 AM, Kagamin wrote:
> > Kai Meyer Wrote:
> >
> >> do all the checking before hand. Arrays are a good example. When not in -release mode, array boundaries are checked upon every access to the array, and an exception is thrown if access goes out of bounds. In -release mode, if you go out of bounds you get a segfault.
> >
> > No, you get a remote root vulnerability.
> 
> Sure, but the point is that arrays can be used in a way that performs as fast as possible, therefore it becomes the programmer's job to ensure that all access to the array is within bounds, which is faster than making the array check itself every time.

It is faster, but I'm not sure it's so much faster to be worth doing. Really, you shouldn't ship unsafe version by default. It should be the user's right, decision and responsibility to do it.
1 2
Next ›   Last »