toString refactor in druntime

Manu via Digitalmars-d digitalmars-d at puremagic.com
Tue Oct 28 16:06:21 PDT 2014


On 28 October 2014 22:51, Steven Schveighoffer via Digitalmars-d
<digitalmars-d at puremagic.com> wrote:
> On 10/27/14 8:01 PM, Manu via Digitalmars-d wrote:
>>
>>   28 October 2014 04:40, Benjamin Thaut via Digitalmars-d
>> <digitalmars-d at puremagic.com> wrote:
>>>
>>> Am 27.10.2014 11:07, schrieb Daniel Murphy:
>>>
>>>> "Benjamin Thaut"  wrote in message news:m2kt16$2566$1 at digitalmars.com...
>>>>>
>>>>>
>>>>> I'm planning on doing a pull request for druntime which rewrites every
>>>>> toString function within druntime to use the new sink signature. That
>>>>> way druntime would cause a lot less allocations which end up beeing
>>>>> garbage right away. Are there any objections against doing so? Any
>>>>> reasons why such a pull request would not get accepted?
>>>>
>>>>
>>>>
>>>> How ugly is it going to be, since druntime can't use std.format?
>>>
>>>
>>>
>>> They wouldn't get any uglier than they already are, because the current
>>> toString functions within druntime also can't use std.format.
>>>
>>> An example would be to toString function of TypInfo_StaticArray:
>>>
>>> override string toString() const
>>> {
>>>          SizeStringBuff tmpBuff = void;
>>>          return value.toString() ~ "[" ~
>>> cast(string)len.sizeToTempString(tmpBuff) ~ "]";
>>> }
>>>
>>> Would be replaced by:
>>>
>>> override void toString(void delegate(const(char)[]) sink) const
>>> {
>>>          SizeStringBuff tmpBuff = void;
>>>          value.toString(sink);
>>>          sink("[");
>>>          sink(cast(string)len.sizeToTempString(tmpBuff));
>>>          sink("]");
>>> }
>>
>>
>> The thing that really worries me about this synk API is that your code
>> here produces (at least) 4 calls to a delegate. That's a lot of
>> indirect function calling, which can be a severe performance hazard on
>> some systems.
>> We're trading out garbage for low-level performance hazards, which may
>> imply a reduction in portability.
>
>
> I think given the circumstances, we are better off. But when we find a
> platform that does perform worse, we can try and implement alternatives. I
> don't want to destroy performance on the platforms we *do* support, for the
> worry that some future platform isn't as friendly to this method.

Video games consoles are very real, and very now.
I suspect they may even represent the largest body of native code in
the world today.

I don't know if 'alternatives' is the right phrase, since this
approach isn't implemented yet, and I wonder if a slightly different
API strategy exists which may not exhibit this problem.


>> But in any case, I think all synk code like this should aim to call
>> the user supplied synk delegate at most *once* per toString.
>> I'd like to see code that used the stack to compose the string
>> locally, then feed it through to the supplied synk delegate in fewer
>> (or one) calls.
>
>
> This is a good goal to have, regardless. The stack is always pretty high
> performing. However, it doesn't scale well. If you look above, the function
> already uses the stack to output the number. It would be trivial to add 2
> chars to put the "[]" there also so only one sink call occurs.

It would be trivial, and that's precisely what I'm suggesting! :)


> But an aggregate which relies on members to output themselves is going to
> have a tough time following this model. Only at the lowest levels can we
> enforce such a rule.

I understand this, which is the main reason I suggest to explore
something other than a delegate based interface.


> Another thing to think about is that the inliner can potentially get rid of
> the cost of delegate calls.

druntime is a binary lib. The inliner has no effect on this equation.


>> Ideally, I guess I'd prefer to see an overload which receives a slice
>> to write to instead and do away with the delegate call. Particularly
>> in druntime, where API and potential platform portability decisions
>> should be *super*conservative.
>
>
> This puts the burden on the caller to ensure enough space is allocated. Or
> you have to reenter the function to finish up the output. Neither of these
> seem like acceptable drawbacks.

Well that's why I open for discussion. I'm sure there's room for
creativity here.

It doesn't seem that unreasonable to reenter the function to me
actually, I'd prefer a second static call in the rare event that a
buffer wasn't big enough, to many indirect calls in every single case.
There's no way that reentry would be slower. It may be more
inconvenient, but I wonder if some API creativity could address
that...?


> What would you propose for such a mechanism? Maybe I'm not thinking of your
> ideal API.

I haven't thought of one I'm really happy with.
I can imagine some 'foolproof' solution at the API level which may
accept some sort of growable string object (which may represent a
stack allocation by default). This could lead to a virtual call if the
buffer needs to grow, but that's not really any worse than a delegate
call, and it's only in the rare case of overflow, rather than many
calls in all cases.


More information about the Digitalmars-d mailing list