Re: toString refactor in druntime

2014-11-08 Thread John Colvin via Digitalmars-d
On Monday, 3 November 2014 at 16:02:23 UTC, Steven Schveighoffer 
wrote:

On 10/31/14 3:07 PM, H. S. Teoh via Digitalmars-d wrote:
On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via 
Digitalmars-d wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:
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?


Why a sink version instead of an Output Range?


To allow toString to be a virtual function, perhaps?

Besides, the sink version basically allows encapsulation of an 
output
range -- instead of calling x.toString(outputRange) you just 
write:


x.toString((const(char)[] data) { outputRange.put(data); });


No, please don't do that. It's put(outputRange, data);

-Steve


Why?


Re: toString refactor in druntime

2014-11-08 Thread Steven Schveighoffer via Digitalmars-d

On 11/8/14 5:25 AM, John Colvin wrote:

On Monday, 3 November 2014 at 16:02:23 UTC, Steven Schveighoffer wrote:

On 10/31/14 3:07 PM, H. S. Teoh via Digitalmars-d wrote:

x.toString((const(char)[] data) { outputRange.put(data); });


No, please don't do that. It's put(outputRange, data);



Why?


import std.range;

struct ORange
{
void put(char) {}
}

static assert(isOutputRange!(ORange, char));

void main()
{
char[] buf = hello.dup;
ORange r;
//r.put(buf); // fails
put(r, buf); // works
}

I have said before, making the UFCS function the same name as the member 
hook it uses was a bad anti-pattern. Now, you must always use the 
non-UFCS version of put, which sucks.


-Steve


Re: toString refactor in druntime

2014-11-07 Thread Manu via Digitalmars-d
On 3 November 2014 19:55, Walter Bright via Digitalmars-d
digitalmars-d@puremagic.com wrote:
 On 11/2/2014 11:45 PM, Manu via Digitalmars-d wrote:

 On 2 November 2014 04:15, Walter Bright via Digitalmars-d
 digitalmars-d@puremagic.com wrote:

 Why would templates make you nervous? They're not C++ templates!

 What do you mean? How are D templates any different than C++ templates
 in a practical sense?


 They're much more straightforward to use, syntactically and semantically.

This isn't anything to do with what I'm talking about. I'm not nervous
because I don't like the D template syntax, it's because I don't feel
it's a good idea for druntime (specifically) to have non-static
interfaces which may expose, or create dependencies on druntime
internals.


 I want a binary lib to be a binary lib. I don't think it's good form
 for the lowest level library in the language ecosystem to depend on
 templates (ie, client-side code generation).


 What's the problem with that?

Templates which operate on library internals within client code create
more dependencies on the library. It obscures the clarity of the API.


 This is the fundamental lib that will be present in every D
 application there is. If it is not a binary lib, then it can't be
 updated.


 Have you ever looked at the C openssl.lib? The .h files with it are loaded
 with metaprogramming done with C macros. Yet I've never heard anyone
 complain about it. C .h files for DLLs are typically stuffed with C macros.

I'm not familiar with openssl, but regardless, I wouldn't consider
openssl to lie at the same level in the ecosystem as druntime.
The only library I would use as comparison is the CRT, which is firmly
self-contained, with a well defined API.

I understand your point in principle; if it is effectively 'helper'
code which may be embedded in the client, *but still operates
exclusively via the API*, then I have no issue. It doesn't appear to
me that this is the case here...?


 Consider performance improvements are made to druntime, which every
 application should enjoy. If the code is templates, then the old
 version at time of compiling is embedded into existing client
 software, the update will have no effect unless the client software is
 rebuilt.
 More important, what about security fixes in druntime... imagine a
 critical security problem in druntime (I bet there's lots!); if we
 can't update druntime, then *every* D application is an exploit. Very
 shaky foundation for an ecosystem...


 The defense presents openssl as Exhibit A!

 (The templates really only present the interface to the dll, not the guts of
 it.)

That's fine. But how is that the case here? Performing a string
conversion implies direct access to the data being converted. If the
function is a template, then my app is directly accessing internal
druntime data.


I don't like being classified as 'the offense', in fact, I'm really,
really tired of unfairly being assigned this position whenever I try
and make a stand for things that matter to me and my kind.
I'm just highlighting an issue I recognise in the API, where there is
no way to get a string out of druntime without GC or indirect function
calls.
It seems to be the appropriate time for me to raise this opinion,
since we seem to be in a full-throttle state of supporting @nogc,
which is excellent, but replacing it with indirect function calls
isn't awesome. This takes one problem and replaces it with a different
problem with different characteristics.

I would like to see an overload like the C lib; API receives memory,
writes to it. This may not be the API that most people use, but I
think it should exist.
I then suggested that this API may be crafted in such a way that the
higher-level goals can also be expressed through it. It could be
wrapped in a little thing that may request a memory expansion if the
buffer supplied wasn't big enough:

struct OutputBuffer
{
  char[] buffer;
  bool function(size_t size) extendBuffer; // - user-supplied
(indirect) function that may expand the buffer in some app-specific
way.
}

toString writes to 'buffer', if it's not big enough, ONLY THEN make an
indirect function call to get more memory.  API is the similar to the
synk callback, but only used in the rare case of overflow.

Perhaps my little example could be re-jigged to support the synk
delegate approach somehow, or perhaps it's just a different overload.

I don't know exactly, other people have much more elaborate needs than
myself. I'm just saying I don't like where this API is headed. It
doesn't satisfy my needs, and I'm super-tired of being vilified for
having that opinion!
I'd be happy with 'toString(char[] outputBuffer)', but I think a
design may exist where all requirements are satisfied, rather than
just dismissing my perspective as niche and annoying, and rolling with
what's easy.

I would propose these criteria:
  * function is not a template exposing druntime internals to the host
application
  * toString should be capable 

Re: toString refactor in druntime

2014-11-07 Thread Walter Bright via Digitalmars-d

On 11/7/2014 5:41 PM, Manu via Digitalmars-d wrote:

On 3 November 2014 19:55, Walter Bright via Digitalmars-d
This isn't anything to do with what I'm talking about. I'm not nervous
because I don't like the D template syntax, it's because I don't feel
it's a good idea for druntime (specifically) to have non-static
interfaces which may expose, or create dependencies on druntime
internals.


My point with the C macro interfaces is it's not the template that makes an 
API design non-static, it's the way the API is designed. Such a determination 
can only be made on a case by case basis, not a blanket templates-are-bad.




The only library I would use as comparison is the CRT, which is firmly
self-contained, with a well defined API.


Hardly. It still uses macros, and it's still quite sensitive to various struct 
declarations which are in the corresponding .h files. If you don't agree, take a 
look in druntime at all the conditional compilation for the various CRTs which 
presumably have the same API.


Check out fileno(), for example. Or errno. Or heck, just grep for #define in the 
C .h files, or grep for struct{.




That's fine. But how is that the case here? Performing a string
conversion implies direct access to the data being converted. If the
function is a template, then my app is directly accessing internal
druntime data.


It all depends on how you design it. Recall that what defines an Output Range is 
the existence of put(r,e). There's no dependency on internals unless you 
deliberately expose it.




I don't like being classified as 'the offense', in fact, I'm really,
really tired of unfairly being assigned this position whenever I try
and make a stand for things that matter to me and my kind.


Sorry, I thought you'd find the metaphor amusing. I didn't mean it to be 
offensive (!). Oops, there I go again!




which is excellent, but replacing it with indirect function calls
isn't awesome. This takes one problem and replaces it with a different
problem with different characteristics.


I think this is a misunderstanding of output ranges.



I would like to see an overload like the C lib; API receives memory,
writes to it. This may not be the API that most people use, but I
think it should exist.
I then suggested that this API may be crafted in such a way that the
higher-level goals can also be expressed through it. It could be
wrapped in a little thing that may request a memory expansion if the
buffer supplied wasn't big enough:

struct OutputBuffer
{
   char[] buffer;
   bool function(size_t size) extendBuffer; // - user-supplied
(indirect) function that may expand the buffer in some app-specific
way.
}

toString writes to 'buffer', if it's not big enough, ONLY THEN make an
indirect function call to get more memory.  API is the similar to the
synk callback, but only used in the rare case of overflow.


We're reinventing Output Ranges again.



I don't know exactly, other people have much more elaborate needs than
myself. I'm just saying I don't like where this API is headed. It
doesn't satisfy my needs, and I'm super-tired of being vilified for
having that opinion!


Well, my opinion that sink should be replaced with output range isn't very 
popular here either, join the club.




   * toString should be capable of receiving a pre-allocated(/stack)
buffer and just write to it
   * indirect function calls should only happen only in the rare case
of output buffer overflow, NOT in all cases


Again, this is reinventing output ranges. It's exactly the niche they serve.



I don't think that's what's on offer here. If toString is a template
(ie, receives an OutputRange, which is a user-defined type), how can
it be that the user's instantiation would already be present in
druntime?


If one uses an output range that is imported from druntime, which is quite 
likely, then it is also quite likely that the instantiation with that type will 
already be present in druntime.




Re: toString refactor in druntime

2014-11-03 Thread Andrei Alexandrescu via Digitalmars-d

On 11/3/14 8:45 AM, Manu via Digitalmars-d wrote:

On 2 November 2014 04:15, Walter Bright via Digitalmars-d
digitalmars-d@puremagic.com wrote:

Why would templates make you nervous? They're not C++ templates!


What do you mean? How are D templates any different than C++ templates
in a practical sense?


Probably about three times easier to read and write.


I want a binary lib to be a binary lib. I don't think it's good form
for the lowest level library in the language ecosystem to depend on
templates (ie, client-side code generation).
This is the fundamental lib that will be present in every D
application there is. If it is not a binary lib, then it can't be
updated.

Consider performance improvements are made to druntime, which every
application should enjoy. If the code is templates, then the old
version at time of compiling is embedded into existing client
software, the update will have no effect unless the client software is
rebuilt.
More important, what about security fixes in druntime... imagine a
critical security problem in druntime (I bet there's lots!); if we
can't update druntime, then *every* D application is an exploit. Very
shaky foundation for an ecosystem...


The same argument goes for all statically linked libraries.


druntime is a fundamental ecosystem library. It should be properly
semantically version-ed, and particularly for security reasons, I
think this should be taken very very seriously.

This argument could equally be applicable to phobos, and I've always
been nervous about it too for the same reasons... but I'll draw a line
there, in that phobos is not critical for an application to build and
link, and so much of the API is already templates, it would be
impossible to change that now.


Within reason, most of the runtime and standard library ought to be 
generic so as to adapt best to application needs. Generics are a very 
powerful mechanism for libraries.



Andrei



Re: toString refactor in druntime

2014-11-03 Thread Walter Bright via Digitalmars-d

On 11/2/2014 11:45 PM, Manu via Digitalmars-d wrote:

On 2 November 2014 04:15, Walter Bright via Digitalmars-d
digitalmars-d@puremagic.com wrote:

Why would templates make you nervous? They're not C++ templates!

What do you mean? How are D templates any different than C++ templates
in a practical sense?


They're much more straightforward to use, syntactically and semantically.



I want a binary lib to be a binary lib. I don't think it's good form
for the lowest level library in the language ecosystem to depend on
templates (ie, client-side code generation).


What's the problem with that?



This is the fundamental lib that will be present in every D
application there is. If it is not a binary lib, then it can't be
updated.


Have you ever looked at the C openssl.lib? The .h files with it are loaded with 
metaprogramming done with C macros. Yet I've never heard anyone complain about 
it. C .h files for DLLs are typically stuffed with C macros.




Consider performance improvements are made to druntime, which every
application should enjoy. If the code is templates, then the old
version at time of compiling is embedded into existing client
software, the update will have no effect unless the client software is
rebuilt.
More important, what about security fixes in druntime... imagine a
critical security problem in druntime (I bet there's lots!); if we
can't update druntime, then *every* D application is an exploit. Very
shaky foundation for an ecosystem...


The defense presents openssl as Exhibit A!

(The templates really only present the interface to the dll, not the guts of 
it.)



druntime is a fundamental ecosystem library. It should be properly
semantically version-ed, and particularly for security reasons, I
think this should be taken very very seriously.


openssl!!!

BTW, you should know that if a template is instantiated by the library itself, 
the compiler won't re-instantiate it and insert that in the calling code. It'll 
just call the instantiated binary.




Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 10/31/14 4:50 PM, Jonathan Marler wrote:


I wrote a Windows CE app to run on our printers here at HP to test what
the Microsoft ARM compiler does with virtual function calls.  I had to
do an operation with a global volatile variable to prevent the compiler
from inlining the non-virtual function call but I finally got it to work.

Calling the function 100 million times yielded the following times:

Windows Compiler on ARM (Release)
---
NonVirtual: 0.537000 seconds
Virtual   : 1.281000 seconds

Windows Compiler on x86 (Release)
---
NonVirtual: 0.226000 seconds
Virtual   : 0.226000 seconds

Windows Compiler on x86 (Debug)
---
NonVirtual: 2.94 seconds
Virtual   : 3.204000 seconds


Here's the link to the code:

http://marler.info/virtualtest.c




Thanks, this is helpful.

-Steve


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

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 

Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 10/31/14 3:04 PM, Walter Bright wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

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?


Why a sink version instead of an Output Range?



A sink is an output range. Supporting all output ranges isn't necessary.

-Steve


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 10/31/14 3:07 PM, H. S. Teoh via Digitalmars-d wrote:

On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via Digitalmars-d wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

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?


Why a sink version instead of an Output Range?


To allow toString to be a virtual function, perhaps?

Besides, the sink version basically allows encapsulation of an output
range -- instead of calling x.toString(outputRange) you just write:

x.toString((const(char)[] data) { outputRange.put(data); });


No, please don't do that. It's put(outputRange, data);

-Steve


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 10/31/14 5:01 PM, Walter Bright wrote:

On 10/31/2014 12:07 PM, H. S. Teoh via Digitalmars-d wrote:

On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via
Digitalmars-d wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

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?


Why a sink version instead of an Output Range?


To allow toString to be a virtual function, perhaps?


Output ranges can be virtual functions. All an output range is is a type
with a put method.


He said toString not sink. And there are more use cases than a type 
that implements 'put'.



What I object to with the sink design is there is no consistency in
design - we cannot preach ranges as a best practice and then use some
other methodology.


Keep in mind that saying toString will take output ranges means that 
ALL toString implementers must handle ALL forms of output ranges. It's 
not an issue with we don't know what we're doing, it's an issue of 
let's not make everyone who wants to spit out a simple string handle 5+ 
different use cases, and you'd better test for them, because the 
compiler won't complain until it's used!


I think toString should be first and foremost SIMPLE. It already was -- 
return a string. But that forces people to allocate, and we want to 
avoid that. Using a sink is pretty much just as simple.



BTW, just to be clear, I applaud fixing druntime to remove unnecessary
GC allocations, and agree that with proper design most of the
allocations can go away. It's just that sink and output ranges are both
designed to solve the same problem in pretty much the same way. The
difference appears to be little more than tomayto tomahto.


It is a huge difference to say EVERYONE who implements toString will 
take any templated type that purports to be an output range, vs giving 
one case to handle.


-Steve


Re: toString refactor in druntime

2014-11-03 Thread Tobias Müller via Digitalmars-d
Walter Bright newshou...@digitalmars.com wrote:

[snip]

 Have you ever looked at the C openssl.lib? The .h files with it are
 loaded with metaprogramming done with C macros. Yet I've never heard
 anyone complain about it.

Those macros are a very common common complaint in my experience.

 C .h files for DLLs are typically stuffed with C macros.

[snip]

 The defense presents openssl as Exhibit A!

Presenting OpenSSL as a case for good interface design is a crime by
itself!

Tobi


Re: toString refactor in druntime

2014-11-03 Thread via Digitalmars-d
On Monday, 3 November 2014 at 15:42:57 UTC, Steven Schveighoffer 
wrote:
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 :)


Note that delegates aren't virtual calls, but indirect calls. The 
former need 2 memory access, the latter none (or 3 vs. 1 if the 
delegate/object isn't yet in a register).


Re: toString refactor in druntime

2014-11-03 Thread Walter Bright via Digitalmars-d

On 11/3/2014 9:36 AM, Tobias Müller wrote:

Presenting OpenSSL as a case for good interface design is a crime by
itself!


Not at all. I presented it as an example of a C library that has a 
metaprogramming interface, but that interface has not prevented bug fix updates 
to the shared library itself without requiring recompiling of apps that call it.


All shared C libraries have a metaprogramming interface if they have macros in 
the .h file.




Re: toString refactor in druntime

2014-11-03 Thread Walter Bright via Digitalmars-d

On 11/3/2014 8:09 AM, Steven Schveighoffer wrote:

It is a huge difference to say EVERYONE who implements toString will take any
templated type that purports to be an output range, vs giving one case to 
handle.


All an output range is is a type with a 'put' method. That's it. You're making 
it out to be far more complex than it is.




Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 11/3/14 4:37 PM, Walter Bright wrote:

On 11/3/2014 9:36 AM, Tobias Müller wrote:

Presenting OpenSSL as a case for good interface design is a crime by
itself!


Not at all. I presented it as an example of a C library that has a
metaprogramming interface, but that interface has not prevented bug fix
updates to the shared library itself without requiring recompiling of
apps that call it.

All shared C libraries have a metaprogramming interface if they have
macros in the .h file.



I had a very nasty experience with using a template-based API. I vowed 
to avoid it wherever possible.


The culprit was std::string -- it changed something internally from one 
version of libc++ to the next on Linux. So I had to recompile 
everything, but the whole system I was using was with .so objects.


templates do NOT make good API types IMO.

-Steve


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 11/3/14 4:40 PM, Walter Bright wrote:

On 11/3/2014 8:09 AM, Steven Schveighoffer wrote:

It is a huge difference to say EVERYONE who implements toString will
take any
templated type that purports to be an output range, vs giving one case
to handle.


All an output range is is a type with a 'put' method. That's it. You're
making it out to be far more complex than it is.



Directly from the docs: 
(http://dlang.org/phobos/std_range.html#isOutputRange)


void myprint(in char[] s) { }
static assert(isOutputRange!(typeof(myprint), char));

No 'put' in sight, except as a substring of isOutputRange.

I don't think you realize what a beast supporting all output ranges is, 
or using them (hint: calling r.put for a generic output range is an ERROR).


-Steve


Re: toString refactor in druntime

2014-11-03 Thread Jonathan Marler via Digitalmars-d
On Monday, 3 November 2014 at 22:33:25 UTC, Steven Schveighoffer 
wrote:

On 11/3/14 4:40 PM, Walter Bright wrote:

On 11/3/2014 8:09 AM, Steven Schveighoffer wrote:
It is a huge difference to say EVERYONE who implements 
toString will

take any
templated type that purports to be an output range, vs giving 
one case

to handle.


All an output range is is a type with a 'put' method. That's 
it. You're

making it out to be far more complex than it is.



Directly from the docs: 
(http://dlang.org/phobos/std_range.html#isOutputRange)


void myprint(in char[] s) { }
static assert(isOutputRange!(typeof(myprint), char));

No 'put' in sight, except as a substring of isOutputRange.

I don't think you realize what a beast supporting all output 
ranges is, or using them (hint: calling r.put for a generic 
output range is an ERROR).


-Steve


In many cases templates are good because they provide the a way 
for the programmer to use a library optimized for their 
particular application.  This is the case for the toString 
function.  An argument can be made that using templates is 
dangerous because if they are used incorrectly, the number of 
template instantiates can blow up.  But this can always be solved 
by the programmer by changing all their template calls to use the 
same template parameters.  This allows the template solution to 
simultaneously support a sink that represents a real function, or 
a delegate, or whatever the application needs.


I understand that people like having a binary library that 
instantiates it's own functions that have a static interface and 
I think there's value to that.  But most of the value is in 
dynamic libraries that the compiler cannot optimize.  When the 
compiler can optimize, let it:)


I updated my test code to use a templated sink, here the link:

http://marler.info/dtostring.d


   Method 1: ReturnString
 string toString();
   Method 2: SinkDelegate
 void toString(void delegate(const(char)[]) sink);
   Method 3: SinkTemplate
 void toString(T)(T sink) 
if(isOutputRange!(T,const(char)[]));

   Method 4: SinkDelegateWithStaticHelperBuffer
 struct SinkStatic { char[64] buffer; void 
delegate(const(char)[]) sink; }

 void toString(ref SinkStatic sink);
   Method 5: SinkDelegateWithDynamicHelperBuffer
 struct SinkDynamic { char[] buffer; void 
delegate(const(char)[]) sink; }

 void toString(ref SinkDynamic sink);
 void toString(SinkDynamic sink);


(DMD Compiler on x86) dmd dtostring.d
RuntimeString run 1 (loopcount 1000)
  Method 1 : 76 ms
  Method 2 : 153 ms
  Method 3 : 146 ms
  Method 4 : 157 ms
  Method 5ref  : 165 ms
  Method 5noref: 172 ms
StringWithPrefix run 1 (loopcount 100)
  Method 1 : 149 ms
  Method 2 : 22 ms
  Method 3 : 21 ms
  Method 4 : 80 ms
  Method 5ref  : 81 ms
  Method 5noref: 82 ms
ArrayOfStrings run 1 (loopcount 100)
  Method 1 : 1 sec
  Method 2 : 81 ms
  Method 3 : 77 ms
  Method 4 : 233 ms
  Method 5ref  : 232 ms
  Method 5noref: 223 ms


(DMD Compiler on x86 with Optimization) dmd -O dtostring.d
RuntimeString run 1 (loopcount 1000)
  Method 1 : 30 ms
  Method 2 : 65 ms
  Method 3 : 55 ms
  Method 4 : 68 ms
  Method 5ref  : 68 ms
  Method 5noref: 67 ms
StringWithPrefix run 1 (loopcount 100)
  Method 1 : 158 ms
  Method 2 : 9 ms
  Method 3 : 8 ms
  Method 4 : 63 ms
  Method 5ref  : 64 ms
  Method 5noref: 66 ms
ArrayOfStrings run 1 (loopcount 100)
  Method 1 : 1 sec, 292 ms
  Method 2 : 35 ms
  Method 3 : 34 ms
  Method 4 : 193 ms
  Method 5ref  : 198 ms
  Method 5noref: 200 ms

The results aren't suprising.  The template out performs the 
delegate sink.  In a very big project one might try to limit the 
number of instantiations of toString by using a specific toString 
instance that accepts some type common OutputRange wrapper which 
would make the template version perform the same as the sink 
delegate version, but for projects that don't need to worry about 
that, you will get better performance from more compiler 
optimization.




Re: toString refactor in druntime

2014-11-03 Thread Walter Bright via Digitalmars-d

On 11/3/2014 2:28 PM, Steven Schveighoffer wrote:

I had a very nasty experience with using a template-based API. I vowed to avoid
it wherever possible.

The culprit was std::string -- it changed something internally from one version
of libc++ to the next on Linux. So I had to recompile everything, but the whole
system I was using was with .so objects.

templates do NOT make good API types IMO.


It seems this is blaming templates for a different problem.

If I have:

  struct S { int x; };

in my C .h file, and I change it to:

  struct S { int x,y; };

Then all my API functions that take S as a value argument will require 
recompilation of any code that uses it.


Would you conclude that C sux for making APIs? Of course not. You'd say that a 
stable API should use reference types, not value types.


Having templates or not is irrelevant.


Re: toString refactor in druntime

2014-11-03 Thread Walter Bright via Digitalmars-d

On 11/3/2014 2:33 PM, Steven Schveighoffer wrote:

On 11/3/14 4:40 PM, Walter Bright wrote:

On 11/3/2014 8:09 AM, Steven Schveighoffer wrote:

It is a huge difference to say EVERYONE who implements toString will
take any
templated type that purports to be an output range, vs giving one case
to handle.


All an output range is is a type with a 'put' method. That's it. You're
making it out to be far more complex than it is.



Directly from the docs: (http://dlang.org/phobos/std_range.html#isOutputRange)

void myprint(in char[] s) { }
static assert(isOutputRange!(typeof(myprint), char));

No 'put' in sight, except as a substring of isOutputRange.

I don't think you realize what a beast supporting all output ranges is, or using
them (hint: calling r.put for a generic output range is an ERROR).


The documentation says, more specifically, that the requirement is that it 
support put(r,e). The array operands are output ranges NOT because the output 
ranges need to know about arrays, but because arrays themselves have a put() 
operation (as defined in std.array).


All the algorithm (such as a toString()) needs to do is call put(r,e). It 
doesn't need to do anything else. It isn't any more complicated than the sink 
interface.


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 11/3/14 8:09 PM, Walter Bright wrote:

On 11/3/2014 2:28 PM, Steven Schveighoffer wrote:

I had a very nasty experience with using a template-based API. I vowed
to avoid
it wherever possible.

The culprit was std::string -- it changed something internally from
one version
of libc++ to the next on Linux. So I had to recompile everything, but
the whole
system I was using was with .so objects.

templates do NOT make good API types IMO.


It seems this is blaming templates for a different problem.

If I have:

   struct S { int x; };

in my C .h file, and I change it to:

   struct S { int x,y; };

Then all my API functions that take S as a value argument will require
recompilation of any code that uses it.

Would you conclude that C sux for making APIs? Of course not. You'd say
that a stable API should use reference types, not value types.


a string is a reference type, the data is on the heap.

But that is not the issue. The issue is that it's IMPOSSIBLE for me to 
ensure std::string remains stable, because it's necessarily completely 
exposed. There is no encapsulation.


Even if I used a pointer to a std::string, the implementation change is 
going to cause issues.


On the contrary, having C-strings as a parameter type has never broken 
for me. In later projects, I have added simple immutable string type 
modeled after D, which works so much better :)


-Steve


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 11/3/14 8:16 PM, Walter Bright wrote:

On 11/3/2014 2:33 PM, Steven Schveighoffer wrote:

On 11/3/14 4:40 PM, Walter Bright wrote:

On 11/3/2014 8:09 AM, Steven Schveighoffer wrote:

It is a huge difference to say EVERYONE who implements toString will
take any
templated type that purports to be an output range, vs giving one case
to handle.


All an output range is is a type with a 'put' method. That's it. You're
making it out to be far more complex than it is.



Directly from the docs:
(http://dlang.org/phobos/std_range.html#isOutputRange)

void myprint(in char[] s) { }
static assert(isOutputRange!(typeof(myprint), char));

No 'put' in sight, except as a substring of isOutputRange.

I don't think you realize what a beast supporting all output ranges
is, or using
them (hint: calling r.put for a generic output range is an ERROR).


The documentation says, more specifically, that the requirement is that
it support put(r,e). The array operands are output ranges NOT because
the output ranges need to know about arrays, but because arrays
themselves have a put() operation (as defined in std.array).


You can't import std.array from druntime.



All the algorithm (such as a toString()) needs to do is call put(r,e).
It doesn't need to do anything else. It isn't any more complicated than
the sink interface.


Again, std.range.put isn't defined in druntime.

And neither is isOutputRange. Are you planning on moving these things to 
druntime?


-Steve


Re: toString refactor in druntime

2014-11-03 Thread Walter Bright via Digitalmars-d

On 11/3/2014 5:47 PM, Steven Schveighoffer wrote:

Again, std.range.put isn't defined in druntime.


True.


And neither is isOutputRange.


Wouldn't really be needed.


Are you planning on moving these things to druntime?


This illustrates a long running issue about what goes in druntime and what in 
phobos.


It's not much of an argument for having a different API that does the same 
thing, only incompatible.




Re: toString refactor in druntime

2014-11-03 Thread H. S. Teoh via Digitalmars-d
On Mon, Nov 03, 2014 at 05:56:56PM -0800, Walter Bright via Digitalmars-d wrote:
 On 11/3/2014 5:47 PM, Steven Schveighoffer wrote:
 Again, std.range.put isn't defined in druntime.
 
 True.
 
 And neither is isOutputRange.
 
 Wouldn't really be needed.
 
 Are you planning on moving these things to druntime?
 
 This illustrates a long running issue about what goes in druntime and
 what in phobos.
[...]

Another prime example is std.typecons.Tuple, which blocked the
implementation of byPair in AA's.


T

-- 
Political correctness: socially-sanctioned hypocrisy.


Re: toString refactor in druntime

2014-11-03 Thread Steven Schveighoffer via Digitalmars-d

On 11/3/14 6:05 PM, Jonathan Marler wrote:

On Monday, 3 November 2014 at 22:33:25 UTC, Steven Schveighoffer wrote:

On 11/3/14 4:40 PM, Walter Bright wrote:

On 11/3/2014 8:09 AM, Steven Schveighoffer wrote:

It is a huge difference to say EVERYONE who implements toString will
take any
templated type that purports to be an output range, vs giving one case
to handle.


All an output range is is a type with a 'put' method. That's it. You're
making it out to be far more complex than it is.



Directly from the docs:
(http://dlang.org/phobos/std_range.html#isOutputRange)

void myprint(in char[] s) { }
static assert(isOutputRange!(typeof(myprint), char));

No 'put' in sight, except as a substring of isOutputRange.

I don't think you realize what a beast supporting all output ranges
is, or using them (hint: calling r.put for a generic output range is
an ERROR).

-Steve


In many cases templates are good because they provide the a way for the
programmer to use a library optimized for their particular application.
This is the case for the toString function.  An argument can be made
that using templates is dangerous because if they are used incorrectly,
the number of template instantiates can blow up.  But this can always be
solved by the programmer by changing all their template calls to use the
same template parameters.  This allows the template solution to
simultaneously support a sink that represents a real function, or a
delegate, or whatever the application needs.


If we make toString a template, we precludes it as a virtual function, 
and we force the object to expose its inner workings.


I think the template solution has advantages, one being the possibility 
for optimization. But I don't think the gains are significant enough. 
It's also more complex than necessary.



I understand that people like having a binary library that instantiates
it's own functions that have a static interface and I think there's
value to that.  But most of the value is in dynamic libraries that the
compiler cannot optimize.  When the compiler can optimize, let it:)

I updated my test code to use a templated sink, here the link:

http://marler.info/dtostring.d


Method 1: ReturnString
  string toString();
Method 2: SinkDelegate
  void toString(void delegate(const(char)[]) sink);
Method 3: SinkTemplate
  void toString(T)(T sink) if(isOutputRange!(T,const(char)[]));
Method 4: SinkDelegateWithStaticHelperBuffer
  struct SinkStatic { char[64] buffer; void
delegate(const(char)[]) sink; }
  void toString(ref SinkStatic sink);
Method 5: SinkDelegateWithDynamicHelperBuffer
  struct SinkDynamic { char[] buffer; void
delegate(const(char)[]) sink; }
  void toString(ref SinkDynamic sink);
  void toString(SinkDynamic sink);


(DMD Compiler on x86) dmd dtostring.d
RuntimeString run 1 (loopcount 1000)
   Method 1 : 76 ms
   Method 2 : 153 ms
   Method 3 : 146 ms
   Method 4 : 157 ms
   Method 5ref  : 165 ms
   Method 5noref: 172 ms
StringWithPrefix run 1 (loopcount 100)
   Method 1 : 149 ms
   Method 2 : 22 ms
   Method 3 : 21 ms
   Method 4 : 80 ms
   Method 5ref  : 81 ms
   Method 5noref: 82 ms
ArrayOfStrings run 1 (loopcount 100)
   Method 1 : 1 sec
   Method 2 : 81 ms
   Method 3 : 77 ms
   Method 4 : 233 ms
   Method 5ref  : 232 ms
   Method 5noref: 223 ms


(DMD Compiler on x86 with Optimization) dmd -O dtostring.d
RuntimeString run 1 (loopcount 1000)
   Method 1 : 30 ms
   Method 2 : 65 ms
   Method 3 : 55 ms
   Method 4 : 68 ms
   Method 5ref  : 68 ms
   Method 5noref: 67 ms
StringWithPrefix run 1 (loopcount 100)
   Method 1 : 158 ms
   Method 2 : 9 ms
   Method 3 : 8 ms
   Method 4 : 63 ms
   Method 5ref  : 64 ms
   Method 5noref: 66 ms
ArrayOfStrings run 1 (loopcount 100)
   Method 1 : 1 sec, 292 ms
   Method 2 : 35 ms
   Method 3 : 34 ms
   Method 4 : 193 ms
   Method 5ref  : 198 ms
   Method 5noref: 200 ms

The results aren't suprising.  The template out performs the delegate
sink.  In a very big project one might try to limit the number of
instantiations of toString by using a specific toString instance that
accepts some type common OutputRange wrapper which would make the
template version perform the same as the sink delegate version, but for
projects that don't need to worry about that, you will get better
performance from more compiler optimization.


I think the performance gains are minimal. The only one that is 
significant is StringWithPrefix, which has a 11% gain. But that's still 
only 1ms, and 1ms on a PC can be attributed to external forces. I would 
increase the loop count on that one.


Note, if you really want to see gains, use -inline.

-Steve


Re: toString refactor in druntime

2014-11-03 Thread Jonathan Marler via Digitalmars-d
On Tuesday, 4 November 2014 at 02:49:55 UTC, Steven Schveighoffer 
wrote:

On 11/3/14 6:05 PM, Jonathan Marler wrote:
In many cases templates are good because they provide the a 
way for the
programmer to use a library optimized for their particular 
application.
This is the case for the toString function.  An argument can 
be made
that using templates is dangerous because if they are used 
incorrectly,
the number of template instantiates can blow up.  But this can 
always be
solved by the programmer by changing all their template calls 
to use the

same template parameters.  This allows the template solution to
simultaneously support a sink that represents a real function, 
or a

delegate, or whatever the application needs.


If we make toString a template, we precludes it as a virtual 
function, and we force the object to expose its inner workings.


I think the template solution has advantages, one being the 
possibility for optimization. But I don't think the gains are 
significant enough. It's also more complex than necessary.




I was thinking you could have the best of both worlds with 
templates.  For example, you could define the toString template 
like this:


  void toStringTemplate(T)(T sink) 
if(isOutputRange!(T,const(char)[]))


Then you could declare an alias like this:

  alias toString = toStringTemplate!(void 
delegate(const(char)[]));


Which (correct me if I'm wrong) I believe is equivalent to the 
original sink delegate function.  This allows programmers to 
write the logic for toString once and allow a developer using the 
library to choose whether they want to use the delegate version 
or the generic output range version.


This gives the user of the library the ability to choose the best 
version for their own application.


Note: I added this alias method to my dtostring.d test code and 
it wasn't as fast as the delegate version.  I'm not sure why as I 
thought the generated code would be identical.  If anyone has any 
insight as to why this happened let me know.


code is at http://marler.info/dtostring.d





Re: toString refactor in druntime

2014-11-03 Thread John Colvin via Digitalmars-d
On Tuesday, 4 November 2014 at 04:34:09 UTC, Jonathan Marler 
wrote:
On Tuesday, 4 November 2014 at 02:49:55 UTC, Steven 
Schveighoffer wrote:

On 11/3/14 6:05 PM, Jonathan Marler wrote:
In many cases templates are good because they provide the a 
way for the
programmer to use a library optimized for their particular 
application.
This is the case for the toString function.  An argument can 
be made
that using templates is dangerous because if they are used 
incorrectly,
the number of template instantiates can blow up.  But this 
can always be
solved by the programmer by changing all their template calls 
to use the
same template parameters.  This allows the template solution 
to
simultaneously support a sink that represents a real 
function, or a

delegate, or whatever the application needs.


If we make toString a template, we precludes it as a virtual 
function, and we force the object to expose its inner workings.


I think the template solution has advantages, one being the 
possibility for optimization. But I don't think the gains are 
significant enough. It's also more complex than necessary.




I was thinking you could have the best of both worlds with 
templates.  For example, you could define the toString template 
like this:


  void toStringTemplate(T)(T sink) 
if(isOutputRange!(T,const(char)[]))


Then you could declare an alias like this:

  alias toString = toStringTemplate!(void 
delegate(const(char)[]));


Which (correct me if I'm wrong) I believe is equivalent to the 
original sink delegate function.  This allows programmers to 
write the logic for toString once and allow a developer using 
the library to choose whether they want to use the delegate 
version or the generic output range version.


This gives the user of the library the ability to choose the 
best version for their own application.


Note: I added this alias method to my dtostring.d test code 
and it wasn't as fast as the delegate version.  I'm not sure 
why as I thought the generated code would be identical.  If 
anyone has any insight as to why this happened let me know.


code is at http://marler.info/dtostring.d


I'm sure it's been mentioned before, but you should try ldc/gdc 
as they have much more capable optimisers.


Re: toString refactor in druntime

2014-11-02 Thread Dicebot via Digitalmars-d

On Saturday, 1 November 2014 at 17:50:33 UTC, Walter Bright wrote:

It is not the same thing as ref/out buffer argument.


Don't understand your comment.


Steven comment has mentioned two things about Tango approach - 
using stack buffer as initial buffer and extensive usage of ref 
parameters for such arguments. std.internal.scopebuffer on its 
own only addresses the former.



We have been running
ping-pong comments about it for a several times now. All
std.internal.scopebuffer does is reducing heap allocation 
count at cost of stack
consumption (and switching to raw malloc for heap) - it does 
not change big-O
estimate of heap allocations unless it is used as a buffer 
argument - at which

point it is no better than plain array.


1. stack allocation is not much of an issue here because these 
routines in druntime are not recursive, and there's plenty of 
stack available for what druntime is using toString for.


Fibers.

Ironically one more problematic parts of Tango for us is the 
Layout one which uses somewhat big stack buffer for formatting 
the arguments into string. It is a very common reason for fiber 
stack overflows (and thus segfaults) unless default size is 
changed.


With 64-bit systems and lazy stack page allocation it is not a 
critical problem anymore but it is still important problem 
because any growth of effective fiber stack prevents one from 
using it as truly cheap context abstraction.


Persistent thread-local heap buffer that gets reused by many 
bound fibers can be much better in that regard.


2. All it does is an inadequate summary. Most of the time it 
completely eliminates the need for allocations. This is a BIG 
deal, and the source of much of the speed of Warp. The cases 
where it does fall back to heap allocation do not leave GC 
memory around.


We have a very different applications in mind. Warp is normal 
single user application and I am speaking about servers. Those 
have very different performance profiles and requirements, 
successful experience in one domain is simply irrelevant to other.


3. There is big-O in worst case, which is very unlikely, and 
big-O in 99% of the cases, which for scopebuffer is O(1).


And with servers you need to always consider worst case as 
default or meet the DoS attack. I am not even sure where 99% 
comes from because input length is usually defined by application 
domain and stack usage of scopebuffer is set inside library code.


4. You're discounting the API of scopebuffer which is usable as 
an output range, fitting it right in with Phobos' pipeline 
design.


Any array is output range :)



Re: toString refactor in druntime

2014-11-02 Thread Walter Bright via Digitalmars-d

On 11/2/2014 3:57 PM, Dicebot wrote:

On Saturday, 1 November 2014 at 17:50:33 UTC, Walter Bright wrote:

It is not the same thing as ref/out buffer argument.


Don't understand your comment.


Steven comment has mentioned two things about Tango approach - using stack
buffer as initial buffer and extensive usage of ref parameters for such
arguments. std.internal.scopebuffer on its own only addresses the former.


I still have no idea how this would apply here.



We have been running
ping-pong comments about it for a several times now. All
std.internal.scopebuffer does is reducing heap allocation count at cost of stack
consumption (and switching to raw malloc for heap) - it does not change big-O
estimate of heap allocations unless it is used as a buffer argument - at which
point it is no better than plain array.


1. stack allocation is not much of an issue here because these routines in
druntime are not recursive, and there's plenty of stack available for what
druntime is using toString for.


Fibers.

Ironically one more problematic parts of Tango for us is the Layout one which
uses somewhat big stack buffer for formatting the arguments into string. It is a
very common reason for fiber stack overflows (and thus segfaults) unless default
size is changed.

With 64-bit systems and lazy stack page allocation it is not a critical problem
anymore but it is still important problem because any growth of effective fiber
stack prevents one from using it as truly cheap context abstraction.

Persistent thread-local heap buffer that gets reused by many bound fibers can be
much better in that regard.


There is no problem with having the max stack allocation for scopebuffer use set 
smaller for 32 bit code than 64 bit code.




2. All it does is an inadequate summary. Most of the time it completely
eliminates the need for allocations. This is a BIG deal, and the source of
much of the speed of Warp. The cases where it does fall back to heap
allocation do not leave GC memory around.

We have a very different applications in mind. Warp is normal single user
application and I am speaking about servers. Those have very different
performance profiles and requirements, successful experience in one domain is
simply irrelevant to other.


What part of druntime would be special case for servers?



3. There is big-O in worst case, which is very unlikely, and big-O in 99% of
the cases, which for scopebuffer is O(1).

And with servers you need to always consider worst case as default or meet the
DoS attack.


Minimizing allocations is about dealing with the most common cases.



I am not even sure where 99% comes from because input length is
usually defined by application domain and stack usage of scopebuffer is set
inside library code.


It's not that hard.



4. You're discounting the API of scopebuffer which is usable as an output
range, fitting it right in with Phobos' pipeline design.


Any array is output range :)


The point is what to do when the array gets full.


Re: toString refactor in druntime

2014-11-02 Thread Manu via Digitalmars-d
On 2 November 2014 04:15, Walter Bright via Digitalmars-d
digitalmars-d@puremagic.com wrote:
 On 11/1/2014 6:35 AM, Manu via Digitalmars-d wrote:

 I'd say that I'd be nervous to see druntime chockers full of templates...?


 What's a chocker?

It's Australian for 'lots'.


 Why would templates make you nervous? They're not C++ templates!

What do you mean? How are D templates any different than C++ templates
in a practical sense?

I want a binary lib to be a binary lib. I don't think it's good form
for the lowest level library in the language ecosystem to depend on
templates (ie, client-side code generation).
This is the fundamental lib that will be present in every D
application there is. If it is not a binary lib, then it can't be
updated.

Consider performance improvements are made to druntime, which every
application should enjoy. If the code is templates, then the old
version at time of compiling is embedded into existing client
software, the update will have no effect unless the client software is
rebuilt.
More important, what about security fixes in druntime... imagine a
critical security problem in druntime (I bet there's lots!); if we
can't update druntime, then *every* D application is an exploit. Very
shaky foundation for an ecosystem...

druntime is a fundamental ecosystem library. It should be properly
semantically version-ed, and particularly for security reasons, I
think this should be taken very very seriously.

This argument could equally be applicable to phobos, and I've always
been nervous about it too for the same reasons... but I'll draw a line
there, in that phobos is not critical for an application to build and
link, and so much of the API is already templates, it would be
impossible to change that now.


Re: toString refactor in druntime

2014-11-01 Thread Jakob Ovrum via Digitalmars-d
On Saturday, 1 November 2014 at 05:27:16 UTC, Jonathan Marler 
wrote:

No need for the extra function, just call:

x.toString((outputRange.put));


That doesn't work for a wide variety of possible cases, notably 
when `put` is a function template or when the code depends on 
std.range.put or some other UFCS `put` function. As such, it 
should be avoided in generic code, and then you might as well 
avoid it in general, lest your algorithm unnecessarily ends up 
breaking with output ranges you didn't test for after refactoring.


(Note that parantheses are not required in your example)


Re: toString refactor in druntime

2014-11-01 Thread Walter Bright via Digitalmars-d

On 10/30/2014 8:30 AM, Steven Schveighoffer wrote:

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.


We keep solving the same problem over and over. std.internal.scopebuffer does 
this handily. It's what it was designed for - it works, it's fast, and it 
virtually eliminates the need for heap allocations. Best of all, it's an Output 
Range, meaning it fits in with the range design of Phobos.




Re: toString refactor in druntime

2014-11-01 Thread Dicebot via Digitalmars-d

On Saturday, 1 November 2014 at 07:02:03 UTC, Walter Bright wrote:

On 10/30/2014 8:30 AM, Steven Schveighoffer wrote:
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.


We keep solving the same problem over and over. 
std.internal.scopebuffer does this handily. It's what it was 
designed for - it works, it's fast, and it virtually eliminates 
the need for heap allocations. Best of all, it's an Output 
Range, meaning it fits in with the range design of Phobos.


It is not the same thing as ref/out buffer argument. We have been 
running ping-pong comments about it for a several times now. All 
std.internal.scopebuffer does is reducing heap allocation count 
at cost of stack consumption (and switching to raw malloc for 
heap) - it does not change big-O estimate of heap allocations 
unless it is used as a buffer argument - at which point it is no 
better than plain array.


Re: toString refactor in druntime

2014-11-01 Thread Jonathan Marler via Digitalmars-d

On Saturday, 1 November 2014 at 06:04:56 UTC, Jakob Ovrum wrote:
On Saturday, 1 November 2014 at 05:27:16 UTC, Jonathan Marler 
wrote:

No need for the extra function, just call:

x.toString((outputRange.put));


That doesn't work for a wide variety of possible cases, notably 
when `put` is a function template or when the code depends on 
std.range.put or some other UFCS `put` function. As such, it 
should be avoided in generic code, and then you might as well 
avoid it in general, lest your algorithm unnecessarily ends up 
breaking with output ranges you didn't test for after 
refactoring.


(Note that parantheses are not required in your example)


Ah yes, you are right that this wouldn't work in generic code.  
Meaning, if the code calling toString was itself a template 
accepting output ranges, then many times the outputRange.put 
wouldn't work.  In this case I think the anonymous function is a 
good way to go.  I was more thinking of the case where the code 
calling toString was user code where the outputRange was a known 
type.  Thanks for catching my silly assumption.


Re: toString refactor in druntime

2014-11-01 Thread Jonathan Marler via Digitalmars-d

On Saturday, 1 November 2014 at 12:31:15 UTC, Dicebot wrote:
On Saturday, 1 November 2014 at 07:02:03 UTC, Walter Bright 
wrote:

On 10/30/2014 8:30 AM, Steven Schveighoffer wrote:
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.


We keep solving the same problem over and over. 
std.internal.scopebuffer does this handily. It's what it was 
designed for - it works, it's fast, and it virtually 
eliminates the need for heap allocations. Best of all, it's an 
Output Range, meaning it fits in with the range design of 
Phobos.


It is not the same thing as ref/out buffer argument. We have 
been running ping-pong comments about it for a several times 
now. All std.internal.scopebuffer does is reducing heap 
allocation count at cost of stack consumption (and switching to 
raw malloc for heap) - it does not change big-O estimate of 
heap allocations unless it is used as a buffer argument - at 
which point it is no better than plain array.


Sorry if this is a stupid question but what's being discussed 
here? Are we talking about passing a scope buffer to toString, or 
are we talking about the implementation of the toString function 
allocating it's own scope buffer?  API change or implementation 
notes or something else?  Thanks.


Re: toString refactor in druntime

2014-11-01 Thread Manu via Digitalmars-d
On 31 October 2014 01:30, Steven Schveighoffer via Digitalmars-d
digitalmars-d@puremagic.com wrote:
 On 10/28/14 7:06 PM, Manu via Digitalmars-d wrote:

 On 28 October 2014 22:51, Steven Schveighoffer via Digitalmars-d
 digitalmars-d@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@puremagic.com wrote:


 Am 27.10.2014 11:07, schrieb Daniel Murphy:

 Benjamin Thaut  wrote in message
 news:m2kt16$2566$1...@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.


 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.

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.

 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.


 Well, the API already exists and is supported. The idea is to migrate the
 existing toString calls to the new API.

Really? Bummer... I haven't seen this API anywhere yet.
Seems a shame to make such a mistake with a brand new API. Too many
competing API patterns :/

 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.


 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.

 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.

 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.


 It depends on the delegate and the item being output, whether the source is
 available to the compiler, and whether or not it's a virtual function. True,
 some cases will not be inlinable. But the tweaks we implement for platform
 X which does not do well with delegate calls, could be to make this more
 available.

I suspect the cases where the inliner can do something useful would be
in quite a significant minority (with respect to 

Re: toString refactor in druntime

2014-11-01 Thread Manu via Digitalmars-d
On 1 November 2014 05:06, via Digitalmars-d digitalmars-d@puremagic.com wrote:
 On Friday, 31 October 2014 at 19:04:29 UTC, Walter Bright wrote:

 On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

 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?


 Why a sink version instead of an Output Range?


 I guess because it's for druntime, and we don't want to pull in std.range?

I'd say that I'd be nervous to see druntime chockers full of templates...?


Re: toString refactor in druntime

2014-11-01 Thread Manu via Digitalmars-d
On 31 October 2014 06:15, Steven Schveighoffer via Digitalmars-d
digitalmars-d@puremagic.com wrote:
 On 10/30/14 2:53 PM, Jonathan Marler wrote:


 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.

 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 think its debatable how useful this information would be but I've
 written a small D Program to try to explore the different performance
 statistics for various methods.  I've uploaded the code to my server,
 feel free to download/modify/use.

 Here's the various methods I've tested.
 /**
 Method 1: ReturnString
   string toString();
 Method 2: SinkDelegate
   void toString(void delegate(const(char)[]) sink);
 Method 3: SinkDelegateWithStaticHelperBuffer
   struct SinkStatic { char[64] buffer; void
 delegate(const(char)[]) sink; }
   void toString(ref SinkStatic sink);
 Method 4: SinkDelegateWithDynamicHelperBuffer
   struct SinkDynamic { char[] buffer; void
 delegate(const(char)[]) sink; }
   void toString(ref SinkDynamic sink);
   void toString(SinkDynamic sink);

   */

 Dmd/No Optimization (dmd dtostring.d):

 RuntimeString run 1 (loopcount 1000)
Method 1 : 76 ms
Method 2 : 153 ms


 I think the above result is deceptive, and the test isn't very useful. The
 RuntimeString toString isn't a very interesting data point -- it's simply a
 single string. Not many cases are like that. Most types have multiple
 members, and it's the need to *construct* a string from that data which is
 usually the issue.

 But I would caution, the whole point of my query was about data on the
 platforms of which Manu speaks. That is, platforms that have issues dealing
 with virtual calls. x86 doesn't seem to be one of them.

 -Steve

I want to get back to this (and other topics), but I'm still about 30
posts behind, and I have to go...
I really can't keep up with this forum these days :/

I'll be back on this topic soon...


Re: toString refactor in druntime

2014-11-01 Thread David Nadlinger via Digitalmars-d

On Saturday, 1 November 2014 at 12:31:15 UTC, Dicebot wrote:
It is not the same thing as ref/out buffer argument. We have 
been running ping-pong comments about it for a several times 
now. All std.internal.scopebuffer does is reducing heap 
allocation count at cost of stack consumption (and switching to 
raw malloc for heap) - it does not change big-O estimate of 
heap allocations unless it is used as a buffer argument - at 
which point it is no better than plain array.


Agreed. Its API also has severe safety problems.

David


Re: toString refactor in druntime

2014-11-01 Thread Walter Bright via Digitalmars-d

On 11/1/2014 5:31 AM, Dicebot wrote:

On Saturday, 1 November 2014 at 07:02:03 UTC, Walter Bright wrote:

On 10/30/2014 8:30 AM, Steven Schveighoffer wrote:

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.


We keep solving the same problem over and over. std.internal.scopebuffer does
this handily. It's what it was designed for - it works, it's fast, and it
virtually eliminates the need for heap allocations. Best of all, it's an
Output Range, meaning it fits in with the range design of Phobos.


It is not the same thing as ref/out buffer argument.


Don't understand your comment.



We have been running
ping-pong comments about it for a several times now. All
std.internal.scopebuffer does is reducing heap allocation count at cost of stack
consumption (and switching to raw malloc for heap) - it does not change big-O
estimate of heap allocations unless it is used as a buffer argument - at which
point it is no better than plain array.


1. stack allocation is not much of an issue here because these routines in 
druntime are not recursive, and there's plenty of stack available for what 
druntime is using toString for.


2. All it does is an inadequate summary. Most of the time it completely 
eliminates the need for allocations. This is a BIG deal, and the source of much 
of the speed of Warp. The cases where it does fall back to heap allocation do 
not leave GC memory around.


3. There is big-O in worst case, which is very unlikely, and big-O in 99% of the 
cases, which for scopebuffer is O(1).


4. You're discounting the API of scopebuffer which is usable as an output range, 
fitting it right in with Phobos' pipeline design.


Furthermore, again, I know it works and is fast.


Re: toString refactor in druntime

2014-11-01 Thread Walter Bright via Digitalmars-d

On 11/1/2014 10:04 AM, David Nadlinger wrote:

Agreed. Its API also has severe safety problems.


Way overblown. And it's nothing that druntime developers cannot handle easily. 
druntime is full of system programming.




Re: toString refactor in druntime

2014-11-01 Thread Walter Bright via Digitalmars-d

On 11/1/2014 6:35 AM, Manu via Digitalmars-d wrote:

I'd say that I'd be nervous to see druntime chockers full of templates...?


What's a chocker?

Why would templates make you nervous? They're not C++ templates!



Re: toString refactor in druntime

2014-10-31 Thread via Digitalmars-d
On Thursday, 30 October 2014 at 20:15:36 UTC, Steven 
Schveighoffer wrote:
I think the above result is deceptive, and the test isn't very 
useful. The RuntimeString toString isn't a very interesting 
data point -- it's simply a single string. Not many cases are 
like that. Most types have multiple members, and it's the need 
to *construct* a string from that data which is usually the 
issue.


But I would caution, the whole point of my query was about data 
on the platforms of which Manu speaks. That is, platforms that 
have issues dealing with virtual calls. x86 doesn't seem to be 
one of them.


OTOH, ArrayOfStrings shows that allocating is worse by several 
orders of magnitudes. This will not change on any architecture. 
And the simple sink variant is still faster than the rest by 
almost an order of magnitude, this may also be unlikely to be 
much different on these architectures.


Re: toString refactor in druntime

2014-10-31 Thread Martin Nowak via Digitalmars-d

On Monday, 27 October 2014 at 07:42:30 UTC, Benjamin Thaut wrote:

Any reasons why such a pull request would not get accepted?

I did that for the exception hierarchy and sure would accept that 
for TypeInfo. Not so sure about Object itself, as it would nail 
down the API.


Re: toString refactor in druntime

2014-10-31 Thread Martin Nowak via Digitalmars-d
On Tuesday, 28 October 2014 at 21:32:14 UTC, ketmar via 
Digitalmars-d wrote:

On Tue, 28 Oct 2014 08:37:43 -0400
I think a few simple functions can suffice for druntime's 
purposes. We don't need a kitchen sink function (pun intended).
ah, those famous last words... ;-) from my observations it's 
enough to
implement '%[+-]width[.maxlen]s' and the same for '%x'. i also 
added
codes to skip arguments and to print all what's left ('%*'). 
i'm sure

that it can be done in 10KB, and it will be very handy to have.
druntime doesn't do alot of printing and string conversions 
anyway. and

phobos is already ridden with templates and CTFE.


https://github.com/D-Programming-Language/druntime/pull/662


Re: toString refactor in druntime

2014-10-31 Thread Martin Nowak via Digitalmars-d
On Tuesday, 28 October 2014 at 23:06:32 UTC, Manu via 
Digitalmars-d wrote:

Video games consoles are very real, and very now.


What architecture/platform?
The indirect function call argument seems a bit displaced for 
toString.


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.


Well the obvious alternative is a printf style return, that tells 
you how much space is needed.



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,


Second virtual call ;).


Re: toString refactor in druntime

2014-10-31 Thread Steven Schveighoffer via Digitalmars-d
On 10/31/14 4:40 AM, Marc =?UTF-8?B?U2Now7x0eiI=?= schue...@gmx.net 
wrote:

On Thursday, 30 October 2014 at 20:15:36 UTC, Steven Schveighoffer wrote:

I think the above result is deceptive, and the test isn't very useful.
The RuntimeString toString isn't a very interesting data point -- it's
simply a single string. Not many cases are like that. Most types have
multiple members, and it's the need to *construct* a string from that
data which is usually the issue.

But I would caution, the whole point of my query was about data on the
platforms of which Manu speaks. That is, platforms that have issues
dealing with virtual calls. x86 doesn't seem to be one of them.


OTOH, ArrayOfStrings shows that allocating is worse by several orders of
magnitudes. This will not change on any architecture. And the simple
sink variant is still faster than the rest by almost an order of
magnitude, this may also be unlikely to be much different on these
architectures.


I find it hard to believe that the delegate version is going to be 
slower than the memory version on any architecture also. But I must 
defer to Manu as the expert in those architectures. This is why I asked 
for a test. The presented data is useful, but not for the purpose of my 
query. I need to know if it performs bad on these platforms, not how it 
performs on x86. We should be willing to entertain other proposals for 
how toString should work, but not if it's proven that what we have will 
suffice.


It should be possible to perform such a test without D support.

In any case, I think there is still room for improvement inside the 
implementations of toString as has been mentioned.


-Steve


Re: toString refactor in druntime

2014-10-31 Thread Walter Bright via Digitalmars-d

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

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?


Why a sink version instead of an Output Range?



Re: toString refactor in druntime

2014-10-31 Thread H. S. Teoh via Digitalmars-d
On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via Digitalmars-d wrote:
 On 10/27/2014 12:42 AM, Benjamin Thaut wrote:
 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?
 
 Why a sink version instead of an Output Range?

To allow toString to be a virtual function, perhaps?

Besides, the sink version basically allows encapsulation of an output
range -- instead of calling x.toString(outputRange) you just write:

x.toString((const(char)[] data) { outputRange.put(data); });

Most of the time, you don't even need to bother with this, because given
an output range, formattedWrite(%s...) will automatically figure out
which overload of toString to call and what arguments to pass to it.


T

-- 
The only difference between male factor and malefactor is just a little 
emptiness inside.


Re: toString refactor in druntime

2014-10-31 Thread via Digitalmars-d

On Friday, 31 October 2014 at 19:04:29 UTC, Walter Bright wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:
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?


Why a sink version instead of an Output Range?


I guess because it's for druntime, and we don't want to pull in 
std.range?


Re: toString refactor in druntime

2014-10-31 Thread Jonathan Marler via Digitalmars-d
On Friday, 31 October 2014 at 11:42:05 UTC, Steven Schveighoffer 
wrote:
On 10/31/14 4:40 AM, Marc =?UTF-8?B?U2Now7x0eiI=?= 
schue...@gmx.net wrote:
On Thursday, 30 October 2014 at 20:15:36 UTC, Steven 
Schveighoffer wrote:
I think the above result is deceptive, and the test isn't 
very useful.
The RuntimeString toString isn't a very interesting data 
point -- it's
simply a single string. Not many cases are like that. Most 
types have
multiple members, and it's the need to *construct* a string 
from that

data which is usually the issue.

But I would caution, the whole point of my query was about 
data on the
platforms of which Manu speaks. That is, platforms that have 
issues
dealing with virtual calls. x86 doesn't seem to be one of 
them.


OTOH, ArrayOfStrings shows that allocating is worse by several 
orders of
magnitudes. This will not change on any architecture. And the 
simple
sink variant is still faster than the rest by almost an order 
of
magnitude, this may also be unlikely to be much different on 
these

architectures.


I find it hard to believe that the delegate version is going to 
be slower than the memory version on any architecture also. But 
I must defer to Manu as the expert in those architectures. This 
is why I asked for a test. The presented data is useful, but 
not for the purpose of my query. I need to know if it performs 
bad on these platforms, not how it performs on x86. We should 
be willing to entertain other proposals for how toString should 
work, but not if it's proven that what we have will suffice.


It should be possible to perform such a test without D support.

In any case, I think there is still room for improvement inside 
the implementations of toString as has been mentioned.


-Steve
I wrote a Windows CE app to run on our printers here at HP to 
test what the Microsoft ARM compiler does with virtual function 
calls.  I had to do an operation with a global volatile variable 
to prevent the compiler from inlining the non-virtual function 
call but I finally got it to work.


Calling the function 100 million times yielded the following 
times:


Windows Compiler on ARM (Release)
---
NonVirtual: 0.537000 seconds
Virtual   : 1.281000 seconds

Windows Compiler on x86 (Release)
---
NonVirtual: 0.226000 seconds
Virtual   : 0.226000 seconds

Windows Compiler on x86 (Debug)
---
NonVirtual: 2.94 seconds
Virtual   : 3.204000 seconds


Here's the link to the code:

http://marler.info/virtualtest.c




Re: toString refactor in druntime

2014-10-31 Thread Walter Bright via Digitalmars-d

On 10/31/2014 12:07 PM, H. S. Teoh via Digitalmars-d wrote:

On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via Digitalmars-d wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

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?


Why a sink version instead of an Output Range?


To allow toString to be a virtual function, perhaps?


Output ranges can be virtual functions. All an output range is is a type with a 
put method.




Besides, the sink version basically allows encapsulation of an output
range


The point of an output range is to encapsulate and abstract. Putting another 
wrapper around it just gives the impression we don't know what we're doing.




-- instead of calling x.toString(outputRange) you just write:

x.toString((const(char)[] data) { outputRange.put(data); });

Most of the time, you don't even need to bother with this, because given
an output range, formattedWrite(%s...) will automatically figure out
which overload of toString to call and what arguments to pass to it.


What I object to with the sink design is there is no consistency in design - we 
cannot preach ranges as a best practice and then use some other methodology.


BTW, just to be clear, I applaud fixing druntime to remove unnecessary GC 
allocations, and agree that with proper design most of the allocations can go 
away. It's just that sink and output ranges are both designed to solve the same 
problem in pretty much the same way. The difference appears to be little more 
than tomayto tomahto.


Re: toString refactor in druntime

2014-10-31 Thread H. S. Teoh via Digitalmars-d
On Fri, Oct 31, 2014 at 02:01:50PM -0700, Walter Bright via Digitalmars-d wrote:
 On 10/31/2014 12:07 PM, H. S. Teoh via Digitalmars-d wrote:
 On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via Digitalmars-d 
 wrote:
 On 10/27/2014 12:42 AM, Benjamin Thaut wrote:
 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?
 
 Why a sink version instead of an Output Range?
 
 To allow toString to be a virtual function, perhaps?
 
 Output ranges can be virtual functions. All an output range is is a
 type with a put method.

The problem is that you don't know the type of the output range in
advance. So you'd have to templatize toString. Which means it can no
longer be virtual.


T

-- 
Дерево держится корнями, а человек - друзьями.


Re: toString refactor in druntime

2014-10-31 Thread Andrei Alexandrescu via Digitalmars-d

On 10/31/14 2:11 PM, H. S. Teoh via Digitalmars-d wrote:

On Fri, Oct 31, 2014 at 02:01:50PM -0700, Walter Bright via Digitalmars-d wrote:

On 10/31/2014 12:07 PM, H. S. Teoh via Digitalmars-d wrote:

On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via Digitalmars-d wrote:

On 10/27/2014 12:42 AM, Benjamin Thaut wrote:

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?


Why a sink version instead of an Output Range?


To allow toString to be a virtual function, perhaps?


Output ranges can be virtual functions. All an output range is is a
type with a put method.


The problem is that you don't know the type of the output range in
advance. So you'd have to templatize toString. Which means it can no
longer be virtual.


Yah, for such stuff a delegate that takes a const(char)[] comes to mind. 
-- Andrei




Re: toString refactor in druntime

2014-10-31 Thread Jakob Ovrum via Digitalmars-d
On Friday, 31 October 2014 at 19:09:28 UTC, H. S. Teoh via 
Digitalmars-d wrote:
Besides, the sink version basically allows encapsulation of an 
output
range -- instead of calling x.toString(outputRange) you just 
write:


x.toString((const(char)[] data) { outputRange.put(data); });


In recent compiler versions we can just write:

x.toString(data = outputRange.put(data));


Re: toString refactor in druntime

2014-10-31 Thread H. S. Teoh via Digitalmars-d
On Fri, Oct 31, 2014 at 02:57:58PM -0700, Andrei Alexandrescu via Digitalmars-d 
wrote:
 On 10/31/14 2:11 PM, H. S. Teoh via Digitalmars-d wrote:
 On Fri, Oct 31, 2014 at 02:01:50PM -0700, Walter Bright via Digitalmars-d 
 wrote:
 On 10/31/2014 12:07 PM, H. S. Teoh via Digitalmars-d wrote:
 On Fri, Oct 31, 2014 at 12:04:24PM -0700, Walter Bright via Digitalmars-d 
 wrote:
 On 10/27/2014 12:42 AM, Benjamin Thaut wrote:
 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?
 
 Why a sink version instead of an Output Range?
 
 To allow toString to be a virtual function, perhaps?
 
 Output ranges can be virtual functions. All an output range is is a
 type with a put method.
 
 The problem is that you don't know the type of the output range in
 advance. So you'd have to templatize toString. Which means it can no
 longer be virtual.
 
 Yah, for such stuff a delegate that takes a const(char)[] comes to
 mind. --
[...]

Which is what the sink version of toString currently does.


T

-- 
Error: Keyboard not attached. Press F1 to continue. -- Yoon Ha Lee, CONLANG


Re: toString refactor in druntime

2014-10-31 Thread Jonathan Marler via Digitalmars-d

On Friday, 31 October 2014 at 22:13:31 UTC, Jakob Ovrum wrote:
On Friday, 31 October 2014 at 19:09:28 UTC, H. S. Teoh via 
Digitalmars-d wrote:
Besides, the sink version basically allows encapsulation of an 
output
range -- instead of calling x.toString(outputRange) you just 
write:


x.toString((const(char)[] data) { outputRange.put(data); });


In recent compiler versions we can just write:

x.toString(data = outputRange.put(data));


No need for the extra function, just call:

x.toString((outputRange.put));


Re: toString refactor in druntime

2014-10-30 Thread Steven Schveighoffer via Digitalmars-d

On 10/28/14 7:06 PM, Manu via Digitalmars-d wrote:

On 28 October 2014 22:51, Steven Schveighoffer via Digitalmars-d
digitalmars-d@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@puremagic.com wrote:


Am 27.10.2014 11:07, schrieb Daniel Murphy:


Benjamin Thaut  wrote in message news:m2kt16$2566$1...@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.


Sorry, I meant future *D supported* platforms, not future 
not-yet-existing platforms.



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.


Well, the API already exists and is supported. The idea is to migrate 
the existing toString calls to the new API.



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.


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.


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?



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.


It depends on the delegate and the item being output, whether the source 
is available to the compiler, and whether or not it's a virtual 
function. True, some cases will not be inlinable. But the tweaks we 
implement for platform X which does not do well with delegate calls, 
could be to make this more available.



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.


A reentrant function has to track the state of what has been output, 
which is horrific in my opinion.



There's no way that reentry would be slower. It may be more
inconvenient, but I wonder if some API creativity could address
that...?


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.


Note, you can always allocate a stack buffer, use an inner 

Re: toString refactor in druntime

2014-10-30 Thread Steven Schveighoffer via Digitalmars-d

On 10/29/14 6:03 AM, Kagamin wrote:

struct Sink
{
char[] buff;
void delegate(in char[]) sink;

void write(in char[] s)
{
   auto len=min(s.length,buff.length);
   buff[0..len]=s[0..len];
   buff=buff[len..$];
   const s1=s[len..$];
   if(s1.length)sink(s1);
}
}

override void toString(ref Sink sink) const
{
value.toString(sink);
sink.write([);
len.toString(sink);
sink.write(]);
}


This would require sink to write the buffer before it's first call, 
since you don't track that.


Wouldn't it be better to track the used length in buff directly so 
write can handle that?


Not a bad idea, BTW.

-Steve


Re: toString refactor in druntime

2014-10-30 Thread Kagamin via Digitalmars-d
On Thursday, 30 October 2014 at 15:32:30 UTC, Steven 
Schveighoffer wrote:
This would require sink to write the buffer before it's first 
call, since you don't track that.


That's delegated to the sink delegate.

Wouldn't it be better to track the used length in buff 
directly so write can handle that?


The used length is tracked by shrinking the buff. This only shows 
the callee's perspective.


Re: toString refactor in druntime

2014-10-30 Thread Kagamin via Digitalmars-d
On Thursday, 30 October 2014 at 15:32:30 UTC, Steven 
Schveighoffer wrote:
This would require sink to write the buffer before it's first 
call, since you don't track that.


Also not always true, depends on sink implementation. If you 
construct string in memory, you can concat the buffered result 
with sunken result any time later.


Re: toString refactor in druntime

2014-10-30 Thread Steven Schveighoffer via Digitalmars-d

On 10/30/14 11:54 AM, Kagamin wrote:

On Thursday, 30 October 2014 at 15:32:30 UTC, Steven Schveighoffer wrote:

This would require sink to write the buffer before it's first call,
since you don't track that.


That's delegated to the sink delegate.


Keep in mind, sink delegate is not a singly implemented function, it's 
implemented wherever the output is done. So it's a lot of boilerplate to 
copy around.





Wouldn't it be better to track the used length in buff directly so
write can handle that?


The used length is tracked by shrinking the buff. This only shows the
callee's perspective.


No, what I mean is:

struct Sink
{
   char[] buff;
   uint used; // buff[used..$] is what your buff used to be.
   void delegate(in char[]) sink;
   ...
}

This way, when write finds it runs out of space, first thing it does is 
sink the buff, then starts sinking the rest. In fact, you can just keep 
using buff once you sink it, to avoid future extra calls to sink.


Note, that this can be implemented right on top of the existing sink 
mechanism.


-Steve


Re: toString refactor in druntime

2014-10-30 Thread Kagamin via Digitalmars-d
On Thursday, 30 October 2014 at 16:03:01 UTC, Steven 
Schveighoffer wrote:
Keep in mind, sink delegate is not a singly implemented 
function, it's implemented wherever the output is done. So it's 
a lot of boilerplate to copy around.


Only 2 lines.

This way, when write finds it runs out of space, first thing it 
does is sink the buff, then starts sinking the rest. In fact, 
you can just keep using buff once you sink it, to avoid future 
extra calls to sink.


I tried to make the code minimalistic. Thought that can give 
advantages.


Re: toString refactor in druntime

2014-10-30 Thread Jonathan Marler via Digitalmars-d

On Thursday, 30 October 2014 at 18:54:00 UTC, Jonathan Marler
wrote:


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.


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 think its debatable how useful this information would be but 
I've written a small D Program to try to explore the different 
performance statistics for various methods.  I've uploaded the 
code to my server, feel free to download/modify/use.



Woops heres the link:
http://marler.info/dtostring.d


Re: toString refactor in druntime

2014-10-30 Thread Jonathan Marler via Digitalmars-d


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.


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 think its debatable how useful this information would be but 
I've written a small D Program to try to explore the different 
performance statistics for various methods.  I've uploaded the 
code to my server, feel free to download/modify/use.


Here's the various methods I've tested.
/**
   Method 1: ReturnString
 string toString();
   Method 2: SinkDelegate
 void toString(void delegate(const(char)[]) sink);
   Method 3: SinkDelegateWithStaticHelperBuffer
 struct SinkStatic { char[64] buffer; void 
delegate(const(char)[]) sink; }

 void toString(ref SinkStatic sink);
   Method 4: SinkDelegateWithDynamicHelperBuffer
 struct SinkDynamic { char[] buffer; void 
delegate(const(char)[]) sink; }

 void toString(ref SinkDynamic sink);
 void toString(SinkDynamic sink);

 */

Dmd/No Optimization (dmd dtostring.d):

RuntimeString run 1 (loopcount 1000)
  Method 1 : 76 ms
  Method 2 : 153 ms
  Method 3 : 156 ms
  Method 4ref  : 157 ms
  Method 4noref: 167 ms
StringWithPrefix run 1 (loopcount 100)
  Method 1 : 159 ms
  Method 2 : 22 ms
  Method 3 : 80 ms
  Method 4ref  : 80 ms
  Method 4noref: 83 ms
ArrayOfStrings run 1 (loopcount 100)
  Method 1 : 1 sec and 266 ms
  Method 2 : 79 ms
  Method 3 : 217 ms
  Method 4ref  : 226 ms
  Method 4noref: 222 ms

Dmd/With Optimization (dmd -O dtostring.d):
RuntimeString run 1 (loopcount 1000)
  Method 1 : 35 ms
  Method 2 : 67 ms
  Method 3 : 67 ms
  Method 4ref  : 72 ms
  Method 4noref: 70 ms
StringWithPrefix run 1 (loopcount 100)
  Method 1 : 154 ms
  Method 2 : 9 ms
  Method 3 : 86 ms
  Method 4ref  : 63 ms
  Method 4noref: 65 ms
ArrayOfStrings run 1 (loopcount 100)
  Method 1 : 1 sec and 252 ms
  Method 2 : 37 ms
  Method 3 : 191 ms
  Method 4ref  : 193 ms
  Method 4noref: 201 ms


I would like to make a note that passing around a stack allocated 
buffer to the various toString methods along with a sink delegate 
may not get much performance benefit.  One reason is because the 
logic can get a little hairy when trying to decide if the buffer 
is large enough for the string (see my code on ArrayOfStrings) 
which creates more code which can slow down the processor simply 
because there is more code memory the processor has to manage.  
Also note that adding a helper method to the buffer/sink delegate 
doesn't help at all since this is equivalent to passing around 
the delegate (meaning you could just create the Buffered sink 
that calls the real delegate when it gets full and pass around 
it's own sink delegate).


Re: toString refactor in druntime

2014-10-30 Thread Steven Schveighoffer via Digitalmars-d

On 10/30/14 2:53 PM, Jonathan Marler wrote:


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.

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 think its debatable how useful this information would be but I've
written a small D Program to try to explore the different performance
statistics for various methods.  I've uploaded the code to my server,
feel free to download/modify/use.

Here's the various methods I've tested.
/**
Method 1: ReturnString
  string toString();
Method 2: SinkDelegate
  void toString(void delegate(const(char)[]) sink);
Method 3: SinkDelegateWithStaticHelperBuffer
  struct SinkStatic { char[64] buffer; void
delegate(const(char)[]) sink; }
  void toString(ref SinkStatic sink);
Method 4: SinkDelegateWithDynamicHelperBuffer
  struct SinkDynamic { char[] buffer; void
delegate(const(char)[]) sink; }
  void toString(ref SinkDynamic sink);
  void toString(SinkDynamic sink);

  */

Dmd/No Optimization (dmd dtostring.d):

RuntimeString run 1 (loopcount 1000)
   Method 1 : 76 ms
   Method 2 : 153 ms


I think the above result is deceptive, and the test isn't very useful. 
The RuntimeString toString isn't a very interesting data point -- it's 
simply a single string. Not many cases are like that. Most types have 
multiple members, and it's the need to *construct* a string from that 
data which is usually the issue.


But I would caution, the whole point of my query was about data on the 
platforms of which Manu speaks. That is, platforms that have issues 
dealing with virtual calls. x86 doesn't seem to be one of them.


-Steve


Re: toString refactor in druntime

2014-10-30 Thread Jonathan Marler via Digitalmars-d
On Thursday, 30 October 2014 at 20:15:36 UTC, Steven 
Schveighoffer wrote:

On 10/30/14 2:53 PM, Jonathan Marler wrote:


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.


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 think its debatable how useful this information would be but 
I've
written a small D Program to try to explore the different 
performance
statistics for various methods.  I've uploaded the code to my 
server,

feel free to download/modify/use.

Here's the various methods I've tested.
/**
   Method 1: ReturnString
 string toString();
   Method 2: SinkDelegate
 void toString(void delegate(const(char)[]) sink);
   Method 3: SinkDelegateWithStaticHelperBuffer
 struct SinkStatic { char[64] buffer; void
delegate(const(char)[]) sink; }
 void toString(ref SinkStatic sink);
   Method 4: SinkDelegateWithDynamicHelperBuffer
 struct SinkDynamic { char[] buffer; void
delegate(const(char)[]) sink; }
 void toString(ref SinkDynamic sink);
 void toString(SinkDynamic sink);

 */

Dmd/No Optimization (dmd dtostring.d):

RuntimeString run 1 (loopcount 1000)
  Method 1 : 76 ms
  Method 2 : 153 ms


I think the above result is deceptive, and the test isn't very 
useful. The RuntimeString toString isn't a very interesting 
data point -- it's simply a single string. Not many cases are 
like that. Most types have multiple members, and it's the need 
to *construct* a string from that data which is usually the 
issue.


But I would caution, the whole point of my query was about data 
on the platforms of which Manu speaks. That is, platforms that 
have issues dealing with virtual calls. x86 doesn't seem to be 
one of them.


-Steve
Like I said I think its debatable how useful this information 
would be.  Yes you are correct the RuntimeString isn't very 
interesting, it's just establishes a base to compare what happens 
in the simplest possible case.  In the real world this would 
almost never happen.  I provided the code to encourage others to 
modify it and maybe post some more interesting cases then what I 
already provided.  I wanted to get some real world examples of 
how each API would change the code.  I would love to see someone 
improve on my implementation on ArrayOfStrings.  I wrote this 
code in a couple hours so I'm sure there alot of room for 
improvement.


Re: toString refactor in druntime

2014-10-29 Thread Kagamin via Digitalmars-d
On Tuesday, 28 October 2014 at 23:06:32 UTC, Manu via 
Digitalmars-d wrote:

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.


If you want to combine two approaches of writing data, you will 
need to write toString logic twice, both variants intertwined on 
every byte written.


Re: toString refactor in druntime

2014-10-29 Thread Kagamin via Digitalmars-d

struct Sink
{
   char[] buff;
   void delegate(in char[]) sink;

   void write(in char[] s)
   {
  auto len=min(s.length,buff.length);
  buff[0..len]=s[0..len];
  buff=buff[len..$];
  const s1=s[len..$];
  if(s1.length)sink(s1);
   }
}

override void toString(ref Sink sink) const
{
   value.toString(sink);
   sink.write([);
   len.toString(sink);
   sink.write(]);
}


Re: toString refactor in druntime

2014-10-28 Thread Steven Schveighoffer via Digitalmars-d

On 10/27/14 6:02 PM, ketmar via Digitalmars-d wrote:

On Mon, 27 Oct 2014 17:04:55 -0400
Steven Schveighoffer via Digitalmars-d digitalmars-d@puremagic.com
wrote:


I think this is overkill for this purpose. We need something simple
to save a few lines of code.

18KB (even less) module which consists mostly of functional templates,
generates nice string mixin and adds part of writef functionality and
can convert nicely formatted strings and args to series of calls to any
function is overkill? ok. who i am to teach people that metaprogramming
is the way to automate boring things...


Meta has a cost with the current compiler. It would be nice if it 
didn't, but I have practical concerns.



sure, i won't force anyone to use that, i can't take the pleasure of
writing sink boilerplate calls from people.


I think a few simple functions can suffice for druntime's purposes. We 
don't need a kitchen sink function (pun intended).


-Steve


Re: toString refactor in druntime

2014-10-28 Thread Steven Schveighoffer via Digitalmars-d

On 10/27/14 8:01 PM, Manu via Digitalmars-d wrote:

  28 October 2014 04:40, Benjamin Thaut via Digitalmars-d
digitalmars-d@puremagic.com wrote:

Am 27.10.2014 11:07, schrieb Daniel Murphy:


Benjamin Thaut  wrote in message news:m2kt16$2566$1...@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.



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.


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.


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



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.


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


-Steve


Re: toString refactor in druntime

2014-10-28 Thread ketmar via Digitalmars-d
On Tue, 28 Oct 2014 08:37:43 -0400
Steven Schveighoffer via Digitalmars-d digitalmars-d@puremagic.com
wrote:

 Meta has a cost with the current compiler. It would be nice if it 
 didn't, but I have practical concerns.
i don't think that there will be alot calls to 'write[f]' anyway. i
know that CTFE is not costless (i once wrote a simple Convey's Life
sample and summoned OOM-killer with CTFE .lif parser! ;-), but this can
be compensated by adding CTFE writef use function-by-function.

 I think a few simple functions can suffice for druntime's purposes. We 
 don't need a kitchen sink function (pun intended).
ah, those famous last words... ;-) from my observations it's enough to
implement '%[+-]width[.maxlen]s' and the same for '%x'. i also added
codes to skip arguments and to print all what's left ('%*'). i'm sure
that it can be done in 10KB, and it will be very handy to have.
druntime doesn't do alot of printing and string conversions anyway. and
phobos is already ridden with templates and CTFE.


signature.asc
Description: PGP signature


Re: toString refactor in druntime

2014-10-28 Thread Manu via Digitalmars-d
On 28 October 2014 22:51, Steven Schveighoffer via Digitalmars-d
digitalmars-d@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@puremagic.com wrote:

 Am 27.10.2014 11:07, schrieb Daniel Murphy:

 Benjamin Thaut  wrote in message news:m2kt16$2566$1...@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.


toString refactor in druntime

2014-10-27 Thread Benjamin Thaut via Digitalmars-d
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?


Kind Regards
Benjamin Thaut


Re: toString refactor in druntime

2014-10-27 Thread Daniel Murphy via Digitalmars-d
Benjamin Thaut  wrote in message news:m2kt16$2566$1...@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?


Re: toString refactor in druntime

2014-10-27 Thread Benjamin Thaut via Digitalmars-d

Am 27.10.2014 11:07, schrieb Daniel Murphy:

Benjamin Thaut  wrote in message news:m2kt16$2566$1...@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 advantage would be that the new version now ideally never allocates. 
While the old version allocated 3 times of which 2 allocations end up 
beeing garbage right away.


Also I rember reading that the long term goal is to convert all toString 
functions to the sink version.


Kind Regards
Benjamin Thaut


Re: toString refactor in druntime

2014-10-27 Thread Daniel Murphy via Digitalmars-d

Benjamin Thaut  wrote in message news:m2m3j2$ciu$1...@digitalmars.com...

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 advantage would be that the new version now ideally never allocates. 
While the old version allocated 3 times of which 2 allocations end up 
beeing garbage right away.


Also I rember reading that the long term goal is to convert all toString 
functions to the sink version.


It's very ugly compared to the formattedWrite version, and it does increase 
the line count compared to the current version (this is the main 
disadvantage of the sink-based toString IMO).  If this is as bad as it gets, 
PR approval shouldn't be a problem. 



Re: toString refactor in druntime

2014-10-27 Thread Steven Schveighoffer via Digitalmars-d

On 10/27/14 2:45 PM, Daniel Murphy wrote:

Benjamin Thaut  wrote in message news:m2m3j2$ciu$1...@digitalmars.com...


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 advantage would be that the new version now ideally never
allocates. While the old version allocated 3 times of which 2
allocations end up beeing garbage right away.

Also I rember reading that the long term goal is to convert all
toString functions to the sink version.


It's very ugly compared to the formattedWrite version, and it does
increase the line count compared to the current version (this is the
main disadvantage of the sink-based toString IMO).  If this is as bad as
it gets, PR approval shouldn't be a problem.


Might I suggest a helper that takes a sink and a variadic list of 
strings, and outputs those strings to the sink in order.


-Steve


Re: toString refactor in druntime

2014-10-27 Thread ketmar via Digitalmars-d
On Mon, 27 Oct 2014 15:20:24 -0400
Steven Schveighoffer via Digitalmars-d digitalmars-d@puremagic.com
wrote:

 Might I suggest a helper that takes a sink and a variadic list of 
 strings, and outputs those strings to the sink in order.
hehe. simple CTFE writef seems to be a perfect fit for druntime. i
implemented very barebone, yet powerful PoC in ~18Kb of code (~500
lines). it can be made even smaller if necessary. then one can use
something like `writef!%s:%5s(sink, msg, code);` and so on.


signature.asc
Description: PGP signature


Re: toString refactor in druntime

2014-10-27 Thread Steven Schveighoffer via Digitalmars-d

On 10/27/14 4:24 PM, ketmar via Digitalmars-d wrote:

On Mon, 27 Oct 2014 15:20:24 -0400
Steven Schveighoffer via Digitalmars-d digitalmars-d@puremagic.com
wrote:


Might I suggest a helper that takes a sink and a variadic list of
strings, and outputs those strings to the sink in order.

hehe. simple CTFE writef seems to be a perfect fit for druntime. i
implemented very barebone, yet powerful PoC in ~18Kb of code (~500
lines). it can be made even smaller if necessary. then one can use
something like `writef!%s:%5s(sink, msg, code);` and so on.



I think this is overkill for this purpose. We need something simple to 
save a few lines of code.


-Steve


Re: toString refactor in druntime

2014-10-27 Thread ketmar via Digitalmars-d
On Mon, 27 Oct 2014 17:04:55 -0400
Steven Schveighoffer via Digitalmars-d digitalmars-d@puremagic.com
wrote:

 I think this is overkill for this purpose. We need something simple
 to save a few lines of code.
18KB (even less) module which consists mostly of functional templates,
generates nice string mixin and adds part of writef functionality and
can convert nicely formatted strings and args to series of calls to any
function is overkill? ok. who i am to teach people that metaprogramming
is the way to automate boring things...

sure, i won't force anyone to use that, i can't take the pleasure of
writing sink boilerplate calls from people.


signature.asc
Description: PGP signature


Re: toString refactor in druntime

2014-10-27 Thread Manu via Digitalmars-d
 28 October 2014 04:40, Benjamin Thaut via Digitalmars-d
digitalmars-d@puremagic.com wrote:
 Am 27.10.2014 11:07, schrieb Daniel Murphy:

 Benjamin Thaut  wrote in message news:m2kt16$2566$1...@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 wonder if the trade-off between flexibility and performance went a
little too far towards flexibility in this case. It's better, but I
don't think it hits the mark, and I'm not sure that hitting the mark
on both issues is impossible.

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.

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.

 The advantage would be that the new version now ideally never allocates.
 While the old version allocated 3 times of which 2 allocations end up beeing
 garbage right away.

 Also I rember reading that the long term goal is to convert all toString
 functions to the sink version.

 Kind Regards
 Benjamin Thaut