Thread overview | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
February 09, 2014 On exceptions in D | ||||
---|---|---|---|---|
| ||||
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 #1 module fast_except; //<<all of the above code here>> void exceptional() { risef("All is lost! PI = %f", 3.17f); } //module #2 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 |
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | 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*? |
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | 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.
|
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | 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.
|
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Benjamin Thaut | 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.
|
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jakob Ovrum | 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. |
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jakob Ovrum | 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 |
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jakob Ovrum | 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 |
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Dmitry Olshansky | > Aye, there is my ingelsh ;)
Runglish :)
|
February 09, 2014 Re: On exceptions in D | ||||
---|---|---|---|---|
| ||||
Posted in reply to Adam D. Ruppe | 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.
|
Copyright © 1999-2021 by the D Language Foundation