toString refactor in druntime

Steven Schveighoffer via Digitalmars-d digitalmars-d at puremagic.com
Mon Nov 3 07:42:57 PST 2014


On 11/1/14 9:30 AM, Manu via Digitalmars-d wrote:
> On 31 October 2014 01:30, Steven Schveighoffer via Digitalmars-d

>>
>> Sorry, I meant future *D supported* platforms, not future not-yet-existing
>> platforms.
>
> I'm not sure what you mean. I've used D on current and existing games
> consoles. I personally think it's one of D's most promising markets...
> if not for just a couple of remaining details.

I don't think D officially supports these platforms. I could be wrong.

> Also, my suggestion will certainly perform better on all platforms.
> There is no platform that can benefit from the existing proposal of an
> indirect function call per write vs something that doesn't.

Performance isn't the only consideration. In your case, it has a higher 
priority than ease of implementation, flexibility, or usability. But 
that's not the case everywhere.

Consider the flip-side: on x86, your mechanism may be a hair faster than 
just having a delegate. Is it worth all the extra trouble for those 
folks to have to save some state or deal with reallocating buffers in 
their toString functions?

>> Before we start ripping apart our existing APIs, can we show that the
>> performance is really going to be so bad? I know virtual calls have a bad
>> reputation, but I hate to make these choices absent real data.
>
> My career for a decade always seems to find it's way back to fighting
> virtual calls. (in proprietary codebases so I can't easily present
> case studies)
> But it's too late now I guess. I should have gotten in when someone
> came up with the idea... I thought it was new.

At the moment, you are stuck with most toString calls allocating on the 
GC every time they are called. I think the virtual call thing should be 
a pleasant improvement :)

But in all seriousness, I am not opposed to an alternative API, but the 
delegate one seems to find the right balance of flexibility and ease of 
implementation.

I think we can use any number of toString APIs, and in fact, we should 
be able to build on top of the delegate version a mechanism to reduce 
(but not eliminate obviously) virtual calls.

>> For instance, D's underlying i/o system uses FILE *, which is about as
>> virtual as you can get. So are you avoiding a virtual call to use a buffer
>> to then pass to a virtual call later?
>
> I do a lot of string processing, but it never finds it's way to a
> FILE*. I don't write console based software.

Just an example. Point taken.

>> A reentrant function has to track the state of what has been output, which
>> is horrific in my opinion.
>
> How so? It doesn't seem that bad to me. We're talking about druntime
> here, the single most used library in the whole ecosystem... that shit
> should be tuned to the max. It doesn't matter how pretty the code is.

Keep in mind that any API addition is something that all users have to 
deal with. If we are talking about a specialized, tuned API that 
druntime and phobos can use, I don't think it would be impossible to 
include this.

But to say we only support horrible allocate-every-toString-call 
mechanism, and please-keep-your-own-state-machine mechanism is not good. 
The main benefit of the delegate approach is that it's easy to 
understand, easy to use, and reasonably efficient. It's a good middle 
ground. It's also easy to implement a sink. Both sides are easy, it 
makes the whole thing more approachable.

>> The largest problem I see is, you may not know before you start generating
>> strings whether it will fit in the buffer, and therefore, you may still end
>> up eventually calling the sink.
>
> Right. The api should be structured to make a virtual call _only_ in
> the rare instance the buffer overflows. That is my suggestion.
> You can be certain to supply a buffer that will not overflow in many/most cases.

I, and I'm sure most of the developers, are open to new ideas to make 
something like this as painless as possible. I still think we should 
keep the delegate mechanism.

>> Note, you can always allocate a stack buffer, use an inner function as a
>> delegate, and get the inliner to remove the indirect calls. Or use an
>> alternative private mechanism to build the data.
>
> We're talking about druntime specifically. It is a binary lib. The
> inliner won't save you.

Let's define the situation here -- there is a boundary in druntime in 
across which no inlining can occur. Before the boundary or after the 
boundary, inlining is fair game.

So for instance, if a druntime object has 3 members it needs to toString 
in order to satisfy it's own toString, those members will probably all 
be druntime objects as well. In which case it can optimize those sub-calls.

And let's also not forget that druntime has template objects in it as 
well, which are ripe for inlining.

This is what I meant.

>> Would you say that *one* delegate call per object output is OK?
>
> I would say that an uncontrollable virtual call is NEVER okay,
> especially in otherwise trivial and such core functions like toString
> in druntime. But one is certainly better than many.

I'm trying to get a feel for how painful this has to be :) If we can 
have one virtual call, it means you can build a mechanism that works 
with delegates + a more efficient one, by just sinking the result of the 
efficient one. This means you can work with the existing APIs right now.

>> This is a typical mechanism that Tango used -- pass in a ref to a dynamic
>> array referencing a stack buffer. If it needed to grow, just update the
>> length, and it moves to the heap. In most cases, the stack buffer is enough.
>> But the idea is to try and minimize the GC allocations, which are
>> performance killers on the current platforms.
>
> I wouldn't hard-code to overflow to the GC heap specifically. It
> should be an API that the user may overflow to wherever they like.

Just keep in mind the clients of this API are on 3 sides:

1. Those who implement the toString call.
2. Those who implement a place for those toString calls to go.
3. Those who wish to put the 2 together.

We want to reduce the burden as much as possible on all of them. We also 
don't want to require implementing ALL these different toString 
mechanisms -- I should be able to implement one of them, and all can use it.

>
>> I think adding the option of using a delegate is not limiting -- you can
>> always, on a platform that needs it, implement a alternative protocol that
>> is internal to druntime. We are not preventing such protocols by adding the
>> delegate version.
>
> You're saying that some platform may need to implement a further
> completely different API? Then no existing code will compile for that
> platform. This is madness. We already have more than enough API's.

You just said you don't use FILE *. Why do we have to ensure all pieces 
of Phobos implement everything you desire when you aren't going to use 
it? I don't think it's madness to *provide* a mechanism for more 
efficient (on some platforms) code, and then ask those who are 
interested to use that mechanism, while not forcing it on all those who 
aren't. You can always submit a pull request to add it where you need 
it! But having an agreed upon API is an important first step. So let's 
get that done.

>> But on our currently supported platforms, the delegate vs. GC call is soo
>> much better. I can't see any reason to avoid the latter.
>
> The latter? (the GC?) .. Sorry, I'm confused.
>

My bad, I meant the former. No wonder you were confused ;)

-Steve


More information about the Digitalmars-d mailing list