www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - On exceptions in D

reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
Split out of "List of Phobos functions that allocate memory?".

To reiterate, here is some critique, compiled:

1. Exceptions are class instances, hence (by default) are allocated on 
GC heap. This is wrong default, GC is no place for temporaries.

2. Stack trace is constructed on throw. User pays no matter if the trace 
is needed or not. This is in the works, thankfully.

3. Turns out message is expected to be a string, formatted apriori:
https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d#L1306
Formatting a string in such setting inevitably allocates and it happens 
at the throw site, even if nobody is using that message down the line.
At least one can override toString...

I thought I'd do something about it.
A seat of pants "solution" to avoid problems 1 and 3 (I do know it can 
break under some unusual circumstances):

module fast_except;

class Failure(T...) : Exception
{
public:
     this()
     {
         super("");
     }
     override void toString(scope void delegate(in char[]) sink) const
     {
         import std.format : formattedWrite;
         sink(typeid(this).name);
         sink(": ");
         formattedWrite(sink, msg, args);
     }
private:
     void assign()(string msg, auto ref T args)
     {
         this.msg = msg;
         this.args = args;
     }
     T args;
}

void risef(T...)(string fmt, T args)
{
     static Failure!T slot;
     if(!slot)
         slot = new Failure!T();
     slot.assign(fmt, args);
     throw slot;
}

Now to testing. I used separate compilation and no optimization flags 
whatsoever, and with the code below I get supposedly ~4  Millions of 
try/catch per second on Linux x64. That is including extra overhead of a 
function call and whatnot.

"Elapsed 2243 msec. Throughput 4.45766e+06/sec"
on
Intel(R) Core(TM) i5-4670 CPU   3.40GHz


module fast_except;

//<<all of the above code here>>

void exceptional()
{
     risef("All is lost! PI = %f", 3.17f);
}


import std.datetime, std.stdio, fast_except, core.runtime;

void main()
{
     //Runtime.traceHandler = null; //seems to change nothing
     int count = 0;
     StopWatch sw = StopWatch();
     sw.start();
     foreach(_; 0 .. 10_000_000)
     {
         try
         {
             exceptional();
         }
         catch(Exception e)
         {
             count++;
         }
     }
     sw.stop();
     writefln("Elapsed %s msec. Throughput %s/sec",
         sw.peek().msecs, count*1e6/sw.peek().usecs);
}

-- 
Dmitry Olshansky
Feb 09 2014
next sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 9 February 2014 at 17:57:23 UTC, Dmitry Olshansky 
wrote:
 1. Exceptions are class instances, hence (by default) are 
 allocated on GC heap. This is wrong default, GC is no place for 
 temporaries.
This isn't accurate. GC is inherent to the `new` operator, not to classes.
 void risef(T...)(string fmt, T args)
raise*?
Feb 09 2014
next sibling parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Sunday, 9 February 2014 at 18:05:42 UTC, Jakob Ovrum wrote:
 This isn't accurate. GC is inherent to the `new` operator, not 
 to classes.
The new operator doesn't necessarily have to GC. It isn't hard to hack it to use another method, even for a particular type of object: http://arsdnet.net/dcode/except.d But this pales in comparison to the wasteful construction of stack traces that is currently done on linux.
Feb 09 2014
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 9 February 2014 at 18:50:57 UTC, Adam D. Ruppe wrote:
 On Sunday, 9 February 2014 at 18:05:42 UTC, Jakob Ovrum wrote:
 This isn't accurate. GC is inherent to the `new` operator, not 
 to classes.
The new operator doesn't necessarily have to GC. It isn't hard to hack it to use another method, even for a particular type of object: http://arsdnet.net/dcode/except.d
The `new` operator is intended to imply the infinite lifetime model as of the deprecation of overloading `new` and `delete`.
Feb 09 2014
prev sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
09-Feb-2014 22:05, Jakob Ovrum пишет:
 On Sunday, 9 February 2014 at 17:57:23 UTC, Dmitry Olshansky wrote:
 1. Exceptions are class instances, hence (by default) are allocated on
 GC heap. This is wrong default, GC is no place for temporaries.
This isn't accurate. GC is inherent to the `new` operator, not to classes.
I'm saying that basically classes imply infinite lifetime model. Then you may work extra hard and do things like emplace and manual allocation.
 void risef(T...)(string fmt, T args)
raise*?
Aye, there is my ingelsh ;) -- Dmitry Olshansky
Feb 09 2014
next sibling parent "Stanislav Blinov" <stanislav.blinov gmail.com> writes:
 Aye, there is my ingelsh ;)
Runglish :)
Feb 09 2014
prev sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 9 February 2014 at 20:26:20 UTC, Dmitry Olshansky 
wrote:
 I'm saying that basically classes imply infinite lifetime 
 model. Then you may work extra hard and do things like emplace 
 and manual allocation.
Infinite lifetime is also only with `new`. The "extra work" with emplace and manual allocation is the domain of library code (e.g. `C c = alloc!C(ctorArgs);`). The only issues with using library code instead of `new` are details like allocation of non-static nested classes and allocation of classes using private constructors etc. It is true that classes rely on *uniqueness* to some extent, but uniqueness is not particular to GC memory.
Feb 09 2014
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
10-Feb-2014 02:25, Jakob Ovrum пишет:
 On Sunday, 9 February 2014 at 20:26:20 UTC, Dmitry Olshansky wrote:
 I'm saying that basically classes imply infinite lifetime model. Then
 you may work extra hard and do things like emplace and manual allocation.
Infinite lifetime is also only with `new`. The "extra work" with emplace and manual allocation is the domain of library code (e.g. `C c = alloc!C(ctorArgs);`).
I thought of this for a while and I think lazly dynamic allocation is still better. First things first - there is still lazy initialization both ways. The only potential gain of statically allocating memory here is on the first exception being thrown, which doesn't gain anything for our prime case of "many exceptions". Lastly if the said exception is never thrown, static allocation would waste more memory of each thread (TLS). This is especially true for cases where not every thread runs the same code (which is not a small part of the landscape).
 The only issues with using library code instead of `new` are details
 like allocation of non-static nested classes and allocation of classes
 using private constructors etc.

 It is true that classes rely on *uniqueness* to some extent, but
 uniqueness is not particular to GC memory.
I'm thinking that I probably should compile this discussion to some implementable enhancement request for Phobos. `cachedException` or some such sounds like something suitable for std.exception. Thoughts? -- Dmitry Olshansky
Feb 11 2014
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
Oops, I've meant to reply to this post:

 I didn't intend to imply compile-time construction, just statically
 allocate `ubyte[__traits(classInstanceSize, Ex)]` and lazily emplace
 `Ex` there instead of GC-allocating with `new`. It would be doable with
 `std.typecons.Scoped` if that type wasn't naively anonymized a couple of
 releases ago.
I thought of this for a while and I think lazly dynamic allocation is still better. First things first - there is still lazy initialization both ways. The only potential gain of statically allocating memory here is on the first exception being thrown, which doesn't gain anything for our prime case of "many exceptions". Lastly if the said exception is never thrown, static allocation would waste more memory of each thread (TLS). This is especially true for cases where not every thread runs the same code (which is not a small part of the landscape). -- Dmitry Olshansky
Feb 11 2014
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Tuesday, 11 February 2014 at 17:03:13 UTC, Dmitry Olshansky 
wrote:
 10-Feb-2014 02:25, Jakob Ovrum пишет:
 On Sunday, 9 February 2014 at 20:26:20 UTC, Dmitry Olshansky 
 wrote:
 I'm saying that basically classes imply infinite lifetime 
 model. Then
 you may work extra hard and do things like emplace and manual 
 allocation.
Infinite lifetime is also only with `new`. The "extra work" with emplace and manual allocation is the domain of library code (e.g. `C c = alloc!C(ctorArgs);`).
I thought of this for a while and I think lazly dynamic allocation is still better. First things first - there is still lazy initialization both ways. The only potential gain of statically allocating memory here is on the first exception being thrown, which doesn't gain anything for our prime case of "many exceptions".
I was thinking of disadvantages such as GC heap fragmentation, accidentally causing collection cycles in response-critical code (though minor when an exception is thrown, for obvious reasons), and being usable for people have disabled the `new` operator in a custom GC-free druntime.
 Lastly if the said exception is never thrown, static allocation 
 would waste more memory of each thread (TLS). This is 
 especially true for cases where not every thread runs the same 
 code (which is not a small part of the landscape).
Yes, this is a killer point. I was saying the exception is only allocated if the template is actually instantiated, and if it is, it's safe to say it's used - but by no means by all threads! So yeah, dynamic allocation it is.
 I'm thinking that I probably should compile this discussion to 
 some
implementable enhancement request for Phobos. Are you talking about `emplace` for nested classes and such?
 `cachedException` or some such sounds like something suitable 
 for std.exception.

 Thoughts?
I like it. I also like the name; I couldn't think of a good name myself... though it does cause some redundancy on use: throw cachedException!MyException(args...); It still needs some changes in druntime/object.d though, to prevent infinite loops on exception chaining.
Feb 11 2014
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/11/14, 9:02 AM, Dmitry Olshansky wrote:
 I'm thinking that I probably should compile this discussion to some
 implementable enhancement request for Phobos.

 `cachedException` or some such sounds like something suitable for
 std.exception.

 Thoughts?
We need the following: * a "bool caught" state in Throwable. Whenever the Throwable is thrown, that is set to false. After the Throwable has been caught (including as part of a larger chain), the runtime sets the flag to true. * a recycleException!E(Args...) factory function that keeps a cache of exceptions of type E. If an exception has been caught, it is considered recyclable and returned. If no caught exception is available, a new one gets created. Andrei
Feb 11 2014
prev sibling next sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 9 February 2014 at 17:57:23 UTC, Dmitry Olshansky 
wrote:
 void risef(T...)(string fmt, T args)
 {
     static Failure!T slot;
     if(!slot)
         slot = new Failure!T();
     slot.assign(fmt, args);
     throw slot;
 }
If the template is instantiated we can say with almost certainty that the function will be called, so it'd be an improvement to allocate the exception instance statically (lazily initialized with emplace), instead of statically allocating just a reference to a GC-allocated instance. Also, it's nice to keep the throw statement in user code, e.g. `throw allocExf(fmt, args);`, as `throw` is a no-return statement like `return`, `assert(false)` etc.
Feb 09 2014
parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
09-Feb-2014 22:14, Jakob Ovrum пишет:
 On Sunday, 9 February 2014 at 17:57:23 UTC, Dmitry Olshansky wrote:
 void risef(T...)(string fmt, T args)
 {
     static Failure!T slot;
     if(!slot)
         slot = new Failure!T();
     slot.assign(fmt, args);
     throw slot;
 }
If the template is instantiated we can say with almost certainty that the function will be called, so it'd be an improvement to allocate the exception instance statically (lazily initialized with emplace), instead of statically allocating just a reference to a GC-allocated instance.
Might be a good idea but compiler is pretty conservative with what can be created at compile-time and emplace may not play nice with CTFE.
 Also, it's nice to keep the throw statement in user code, e.g. `throw
 allocExf(fmt, args);`, as `throw` is a no-return statement like
 `return`, `assert(false)` etc.
Good idea. Then call it 'exception(f)' and use like this: throw exceptionf("Message with some %s substitution", "writef-like"); -- Dmitry Olshansky
Feb 09 2014
parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Sunday, 9 February 2014 at 20:32:54 UTC, Dmitry Olshansky 
wrote:
 Might be a good idea but compiler is pretty conservative with 
 what can be created at compile-time and emplace may not play 
 nice with CTFE.
I didn't intend to imply compile-time construction, just statically allocate `ubyte[__traits(classInstanceSize, Ex)]` and lazily emplace `Ex` there instead of GC-allocating with `new`. It would be doable with `std.typecons.Scoped` if that type wasn't naively anonymized a couple of releases ago.
Feb 09 2014
prev sibling next sibling parent reply Benjamin Thaut <code benjamin-thaut.de> writes:
Am 09.02.2014 18:57, schrieb Dmitry Olshansky:

 2. Stack trace is constructed on throw. User pays no matter if the trace
 is needed or not. This is in the works, thankfully.
This is not entierly true. The stack trace only stores the addresses of the functions on the stack upon throw. The full stack trace is constructed lazy.
Feb 09 2014
parent reply "Adam D. Ruppe" <destructionator gmail.com> writes:
On Sunday, 9 February 2014 at 18:26:59 UTC, Benjamin Thaut wrote:
 The full stack trace is constructed lazy.
On Windows yes (though StackWalk64 is slow too, I see you or someone else has already fixed that on git), but on Linux it constructed the full string in the constructor! I have a pull request pending some minor details to be worked out that will fix that. Gives a 20-80x improvement on thrown then caught exceptions without printing them.
Feb 09 2014
parent Benjamin Thaut <code benjamin-thaut.de> writes:
Am 09.02.2014 19:49, schrieb Adam D. Ruppe:
 On Sunday, 9 February 2014 at 18:26:59 UTC, Benjamin Thaut wrote:
 The full stack trace is constructed lazy.
On Windows yes (though StackWalk64 is slow too, I see you or someone else has already fixed that on git), but on Linux it constructed the full string in the constructor! I have a pull request pending some minor details to be worked out that will fix that. Gives a 20-80x improvement on thrown then caught exceptions without printing them.
Yes I fixed that for windows. When skimming through the linux source code, I got the impression that lazy creation was implemented for linux too? I must have misread that. Also the latest windows version does use RtlCaptureStackBackTrace if possible, which is a lot faster then StackWalk64.
Feb 09 2014
prev sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, February 09, 2014 21:57:13 Dmitry Olshansky wrote:
 Split out of "List of Phobos functions that allocate memory?".
 
 To reiterate, here is some critique, compiled:
 
 1. Exceptions are class instances, hence (by default) are allocated on
 GC heap. This is wrong default, GC is no place for temporaries.
The more I think about it, the less I'm convinced that this is a big deal. Sure, it would be nice if they were malloc-ed and reference-counted so that they'd go away immediately after they were used and didn't risk triggering a collection, but for exceptions to work right, they need inheritance, so they have to be classes, and since exceptions are generally rare, having the extra overhead of the memory allocation usually isn't a big deal, and if it triggers a collection or sticks around for a little while after it's used (because no collection gets run), most code won't care. The code that really cares is code that's having to throw exceptions more frequently and/or in situations where those exceptions really need to be fast. Personally, I've only run into that in unit tests, and I don't think that the GC has much do with that slowness (and Adam Ruppe's current work on that seems to support that it's not the GC that's an issue). So, if the stack traces (or whatever it is that's making them slow) can be fixed to be faster, then as far as unit tests go, I would consider the matter taken care of and the fact that the GC is used for exceptions to be a non-issue. The place where this becomes an issue then is code that needs exceptions to be really fast (e.g. it sounds like vibe.d falls in that camp). And in that case, it doesn't really matter whether the exceptions are allocated on the GC heap or malloc's heap. If memory allocation is slowing them down, then they need to get rid of the memory allocation entirely, in which case, doing something like having a pool of pre-allocated exception objects to reuse would make a lot more sense. And in that case, it would probably be better if they weren't on the GC heap, but the exception-throwing code wouldn't really care either way. That would be up to the pool. The same goes if only a single, static exception were used. It might be marginally better if it weren't on the GC heap, because it would avoid being scanned, but in those cases where you want speed, you _want_ long lifetimes for the exceptions, not short lifetimes like you're suggesting, because you want to reuse the exceptions in order to avoid needing to allocate new ones. The only way that short lifetimes would work is if we weren't dealing with classes and the exceptions were on the stack, but that negates our ability to have an exception hierarchy - which is critical to how exceptions work. And if some code is getting exceptions frequently enough that the memory allocation is the bottleneck, then maybe exceptions aren't the best choice either. I agree that exceptions need to be much, much faster than they are, but they're still intended for the error case, which should be relatively infrequent.
 2. Stack trace is constructed on throw. User pays no matter if the trace
 is needed or not. This is in the works, thankfully.
Yes, which should be a significant improvement and likely a much larger gain than any memory allocation issues.
 3. Turns out message is expected to be a string, formatted apriori:
 https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d
 #L1306 Formatting a string in such setting inevitably allocates and it
 happens at the throw site, even if nobody is using that message down the
 line. At least one can override toString...
Ideally, creating the string that toString returns would be put off until toString is called (particularly since that includes the stack trace), but I would hope that creating the message string to pass to the exception's constructor would be cheap enough (particularly in light of the fact that the exception is heap-allocated anyway) that it wouldn't be a big deal. So, if we can find a way to make this more efficient without getting messy, that's great, but I wouldn't expect that to be a bottleneck just so long as the actual string that the message gets put into for toString to return (which then includes the file and line and stacktrace and whatnot) isn't created until toString is called. - Jonathan M Davis
Feb 09 2014
next sibling parent reply "inout" <inout gmail.com> writes:
On Monday, 10 February 2014 at 01:16:27 UTC, Jonathan M Davis
wrote:
 On Sunday, February 09, 2014 21:57:13 Dmitry Olshansky wrote:
 Split out of "List of Phobos functions that allocate memory?".
 
 To reiterate, here is some critique, compiled:
 
 1. Exceptions are class instances, hence (by default) are 
 allocated on
 GC heap. This is wrong default, GC is no place for temporaries.
The more I think about it, the less I'm convinced that this is a big deal. Sure, it would be nice if they were malloc-ed and reference-counted so that they'd go away immediately after they were used and didn't risk triggering a collection, but for exceptions to work right, they need inheritance, so they have to be classes, and since exceptions are generally rare, having the extra overhead of the memory allocation usually isn't a big deal, and if it triggers a collection or sticks around for a little while after it's used (because no collection gets run), most code won't care. The code that really cares is code that's having to throw exceptions more frequently and/or in situations where those exceptions really need to be fast. Personally, I've only run into that in unit tests, and I don't think that the GC has much do with that slowness (and Adam Ruppe's current work on that seems to support that it's not the GC that's an issue). So, if the stack traces (or whatever it is that's making them slow) can be fixed to be faster, then as far as unit tests go, I would consider the matter taken care of and the fact that the GC is used for exceptions to be a non-issue. The place where this becomes an issue then is code that needs exceptions to be really fast (e.g. it sounds like vibe.d falls in that camp). And in that case, it doesn't really matter whether the exceptions are allocated on the GC heap or malloc's heap. If memory allocation is slowing them down, then they need to get rid of the memory allocation entirely, in which case, doing something like having a pool of pre-allocated exception objects to reuse would make a lot more sense. And in that case, it would probably be better if they weren't on the GC heap, but the exception-throwing code wouldn't really care either way. That would be up to the pool. The same goes if only a single, static exception were used. It might be marginally better if it weren't on the GC heap, because it would avoid being scanned, but in those cases where you want speed, you _want_ long lifetimes for the exceptions, not short lifetimes like you're suggesting, because you want to reuse the exceptions in order to avoid needing to allocate new ones. The only way that short lifetimes would work is if we weren't dealing with classes and the exceptions were on the stack, but that negates our ability to have an exception hierarchy - which is critical to how exceptions work. And if some code is getting exceptions frequently enough that the memory allocation is the bottleneck, then maybe exceptions aren't the best choice either. I agree that exceptions need to be much, much faster than they are, but they're still intended for the error case, which should be relatively infrequent.
 2. Stack trace is constructed on throw. User pays no matter if 
 the trace
 is needed or not. This is in the works, thankfully.
Yes, which should be a significant improvement and likely a much larger gain than any memory allocation issues.
 3. Turns out message is expected to be a string, formatted 
 apriori:
 https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d
 #L1306 Formatting a string in such setting inevitably 
 allocates and it
 happens at the throw site, even if nobody is using that 
 message down the
 line. At least one can override toString...
Ideally, creating the string that toString returns would be put off until toString is called (particularly since that includes the stack trace), but I would hope that creating the message string to pass to the exception's constructor would be cheap enough (particularly in light of the fact that the exception is heap-allocated anyway) that it wouldn't be a big deal. So, if we can find a way to make this more efficient without getting messy, that's great, but I wouldn't expect that to be a bottleneck just so long as the actual string that the message gets put into for toString to return (which then includes the file and line and stacktrace and whatnot) isn't created until toString is called. - Jonathan M Davis
All that wouldn't be a problem if D had ARC for everything, including Exception`s.
Feb 09 2014
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, February 10, 2014 01:49:40 inout wrote:
 All that wouldn't be a problem if D had ARC for everything,
 including Exception`s.
Well, yes and no. ARC would make it so that your typical exception would be freed right after you're done with it, which would be great, but given that those should be fairly rare, and they usually aren't super performance sensitive, having them be managed by the GC isn't all that big a deal IMHO. Where it matters is when you would want to avoid allocating when throwing, in which case, you wouldn't want ARC - or at least you wouldn't care if it was ARC or managed by the GC - because in that case, you'd be looking to avoid the allocation cost by pre-allocating the exceptions and reusing them, and at that point, ARC buys you nothing over the GC except that it avoids having that memory scanned by the GC on a collection (and ARC could even incur some minor overhead when it has to do the ref-counting). So, I don't really see ARC as a solution here - not to the code that's performance-sensitive enough to be worried about the heap allocation anyway. - Jonathan M Davis
Feb 09 2014
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
10-Feb-2014 07:09, Jonathan M Davis пишет:
 but given that
 those should be fairly rare, and they usually aren't super performance
 sensitive, having them be managed by the GC isn't all that big a deal IMHO.
Let's please stop this trend. A built-in feature should be made as fast as possible, no amount of good excuses will let us avoid that. If we all agree that having some sort of pool for exceptions is what it takes why putting the burden on all our users to reinvent it? Moreover the user in question must also redo a bit of homework to understand that allocating exceptions is what slows his/her code down. -- Dmitry Olshansky
Feb 10 2014
prev sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
10-Feb-2014 05:16, Jonathan M Davis пишет:
 On Sunday, February 09, 2014 21:57:13 Dmitry Olshansky wrote:
 Split out of "List of Phobos functions that allocate memory?".

 To reiterate, here is some critique, compiled:

 1. Exceptions are class instances, hence (by default) are allocated on
 GC heap. This is wrong default, GC is no place for temporaries.
 The place where this becomes an issue then is code that needs exceptions to be
 really fast (e.g. it sounds like vibe.d falls in that camp). And in that case,
 it doesn't really matter whether the exceptions are allocated on the GC heap
 or malloc's heap. If memory allocation is slowing them down, then they need to
 get rid of the memory allocation entirely, in which case, doing something like
 having a pool of pre-allocated exception objects to reuse would make a lot
 more sense.
Yes, but this is more of reuse of the memory and allocating it cheaply. Pools are especially good for objects with short lifetime.
 And in that case, it would probably be better if they weren't on
 the GC heap, but the exception-throwing code wouldn't really care either way.
 That would be up to the pool. The same goes if only a single, static exception
 were used. It might be marginally better if it weren't on the GC heap, because
 it would avoid being scanned, but in those cases where you want speed, you
 _want_ long lifetimes for the exceptions, not short lifetimes like you're
 suggesting, because you want to reuse the exceptions in order to avoid needing
 to allocate new ones.
Exceptions have short lifetimes, that's a simple fact that can be inferred poking at code bases. Their lifespan in most cases is from throw statement to the next catch. Now if memory is preallocated or not has no bearing to lifetime, as each time an exception is thrown with different data means this is a "new" exception regardless of what memory is being reused.
 The only way that short lifetimes would work is if we
 weren't dealing with classes and the exceptions were on the stack, but that
 negates our ability to have an exception hierarchy - which is critical to how
 exceptions work.
I see you imply that class hierarchy and polymorphism is achieved only with classes. This is a good point. I wonder if virtual functions mechanism can be generalized beyond classes.
 And if some code is getting exceptions frequently enough that the memory
 allocation is the bottleneck, then maybe exceptions aren't the best choice
 either.
If the allocation is the bottleneck - cure the allocator.
 I agree that exceptions need to be much, much faster than they are,
 but they're still intended for the error case, which should be relatively
 infrequent.
Exceptions are a mechanism of error handling that has an advantage of cleanly propagating information up across stack frames to the actual point where it could be deal with. Yes, stack unwind implies that it shouldn't probably be 99% of cases.
 3. Turns out message is expected to be a string, formatted apriori:
 https://github.com/D-Programming-Language/druntime/blob/master/src/object_.d
 #L1306 Formatting a string in such setting inevitably allocates and it
 happens at the throw site, even if nobody is using that message down the
 line. At least one can override toString...
Ideally, creating the string that toString returns would be put off until toString is called (particularly since that includes the stack trace), but I would hope that creating the message string to pass to the exception's constructor would be cheap enough (particularly in light of the fact that the exception is heap-allocated anyway)
Here it goes - bah if we allocate why not allocate twice. I'm out of words on this point. -- Dmitry Olshansky
Feb 10 2014