Thoughts about D

H. S. Teoh hsteoh at quickfur.ath.cx
Thu Nov 30 16:12:04 UTC 2017


On Wed, Nov 29, 2017 at 07:29:56PM -0800, Walter Bright via Digitalmars-d wrote:
> On 11/29/2017 7:15 PM, Jonathan M Davis wrote:
> > I wouldn't have expected assertions to cost much more than however
> > much it costs to evaluate the expression being asserted unless the
> > assertion fails.  Now, even that can slow down a program a fair bit,
> > depending on what's being asserted and how many assertions there
> > are, but it's not something that I would have expected to vary
> > particular between C and D. It doesn't surprise me that the
> > generated code would be larger than you'd get for the same
> > assertions in C because how assertions are handled when they fail is
> > quite different, but I would expect the assertions themselves to
> > cost about the same in terms of performance as long as they don't
> > fail. What's going on that's making them so much worse?
> 
> The code *size* causes problems because it pushes the executing code
> out of the cache. Another issue (I should check this again) was doing
> null checks on member function calls, which is not necessary since if
> they're null it'll seg fault.

Can you elaborate?  Because in my current understanding, assert(expr) is
implemented by evaluating expr, which is unavoidable, and if it fails,
calls a function in druntime to handle the failure. So as far as the
user's code is concerned, there shouldn't be any performance issue --
presumably, druntime's assert() implementation shouldn't even be in the
cache because it's not being used (up to that point).  It's just a
single function call in the user's code, which is at most just a few
bytes.

What happens inside the assert() implementation seems to be irrelevant
because at that point your program is going to terminate anyway. So a
cache miss for the assert() implementation isn't going to be a big deal
(unless your program is asserting at a high frequency, in which case you
have bigger problems than performance!).

Unless you're talking about applications where the entire program must
fit in cache or flash or SRAM or whatever. In that case, perhaps the
solution is to have a different druntime that has a leaner
implementation of assert().

Which brings us to the implementation of assert() itself. What about it
makes it so big? I suspect most of the bloat comes from throwing
AssertError, which pulls in the stack-unwinding code, which, if my
memory is still up to date, suffers from performance issues where it
tries to construct the stacktrace regardless of whether or not the catch
block actually wants the stacktrace.  I vaguely remember suggesting that
this should be done lazily, so that the actual construction of the
stacktrace (including symbol lookups, etc.) isn't done until somebody
actually asks for it. You'd still have to save the addresses of the call
somewhere, since otherwise it might get overwritten by the time the
stack unwinding is done, but it should be a lot cheaper than doing
symbol lookups eagerly. But perhaps this issue has since been fixed?

But of course, this assumes that we even need to throw AssertError in
the first place.  If this can be made optional, we can skip the stack
unwinding code altogether. (But I can see that this will only work for
specific applications, since you may not be able to avoid the need for
the unwinding code to call dtors and stuff to free up allocated
resources, etc., which, if it's necessary, means you can't avoid linking
in the stack unwinding code. But it *can*, at least in theory, be
something separate from the stacktrace construction code, so you can
still save a bit of code there. Make the stacktrace construction code a
zero-argument template, then it won't get linked into the executable
unless it's actually used.)


T

-- 
Let's not fight disease by killing the patient. -- Sean 'Shaleh' Perry


More information about the Digitalmars-d mailing list