March 14, 2014
On 14 March 2014 16:39, Walter Bright <newshound2@digitalmars.com> wrote:

> On 3/12/2014 11:44 PM, Manu wrote:
>
>> But you understand the danger in creating a situation where experts can't
>> optimise their code even if they want to; if at some later time it
>> becomes an
>> issue, or some new customer comes along with more stringent requirements.
>> These are not unrealistic hypothetical scenarios. Libraries exist, and
>> they have
>> customers by definition. Requirements change over time. Defaulting to an
>> inflexible position is dangerous.
>>
>
> As I pointed out in another post, virtuality is hardly the only thing that can change in an interface that strongly affects performance. The existence of destructors is another. About everything about an interface affects performance.
>

In my experience, API layout is the sort of performance detail that library
authors are much more likely to carefully consider and get right. It's
higher level, easier to understand, and affects all architectures equally.
It's also something that they teach in uni. People write books about that
sort of thing.
Not to say there aren't terrible API designs out there, but D doesn't make
terrible-api-design-by-default a feature.
Stuff like virtual is the sort of thing that only gets addressed when it is
reported by a user that cares, and library authors are terribly reluctant
to implement a breaking change because some user reported it. I know this
from experience.
I can say with confidence, poor API design has caused me less problems than
virtual in my career.

Can you honestly tell me that you truly believe that library authors will
consider, as a matter of common sense, the implications of virtual (the
silent default state) in their api?
Do you truly believe that I'm making a big deal out of nothing; that I will
never actually, in practise, encounter trivial accessors and properties
that can't inline appearing in my hot loops, or other related issues.

Inline-ability is a very strong API level performance influence, especially in a language with properties.

Most programmers are not low-level experts, they don't know how to protect themselves from this sort of thing. Honestly, almost everyone will just stick with the default.


March 14, 2014
On Fri, 14 Mar 2014 10:22:40 -0000, 1100110 <0b1100110@gmail.com> wrote:

> On 3/14/14, 4:58, Regan Heath wrote:
>>
>> Maintenance is very slightly better too, IMO, because you
>> add/remove/alter a complete line rather than editing a set of || && etc
>> which can in some cases be a little confusing.  Basically, the chance of
>> an error is very slightly lower.
>>
>> For example, either this:
>>
>> version(X86) version = MeaningfulVersion
>> version(X86_64) version = MeaningfulVersion
>> version(PPC) version = MeaningfulVersion
>> version(PPC64) version = MeaningfulVersion
>> version(ARM) version = MeaningfulVersion
>> version(AArch64) version = MeaningfulVersion
>>
>> version(MeaningfulVersion)
>> {
>> }
>> else version (MIPS32)
>> {
>> }
>>
>> or this:
>>
>> version (X86) version = MeaningfulVersion
>> version (X86_64) version = MeaningfulVersion
>> version (PPC) version = MeaningfulVersion
>> version (PPC64) version = MeaningfulVersion
>> version (ARM) version = MeaningfulVersion
>> version (AArch64) version = MeaningfulVersion
>>
>> version (MIPS32) version = OtherMeaningfulVersion
>>
>> version (MeaningfulVersion)
>> {
>> }
>> else version (OtherMeaningfulVersion)
>> {
>> }
>>
>> Regan
>>
>
>
> ...I can't even begin to describe how much more readable the OR'd version is.

It's shorter, but shorter does not mean more "readable".. if by readable you mean include the ability to communicate intent etc.  Add to that, that readable is just one metric.

Walter's point is that the above pattern is better at communicating intent, clarifying your logic, and making the resulting version statements easier to understand (aka "more readable")

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
March 14, 2014
On Fri, 14 Mar 2014 11:37:07 -0000, Daniel Murphy <yebbliesnospam@gmail.com> wrote:

> "Walter Bright"  wrote in message news:lfu74a$8cr$1@digitalmars.com...
>
>> > No, it doesn't, because it is not usable if C introduces any virtual methods.
>>
>> That's what the !final storage class is for.
>
> My mistake, I forgot you'd said you were in favor of this.  Being able to 'escape' final certainly gets us most of the way there.
>
> !final is really rather hideous though.

+1 eew.

R

-- 
Using Opera's revolutionary email client: http://www.opera.com/mail/
March 14, 2014
On Fri, 14 Mar 2014 05:35:03 -0400, Don <x@nospam.com> wrote:

> On Thursday, 13 March 2014 at 13:47:13 UTC, Steven Schveighoffer wrote:
>> On Thu, 13 Mar 2014 09:37:51 -0400, Dicebot <public@dicebot.lv> wrote:
>>
>>> On Thursday, 13 March 2014 at 13:16:54 UTC, Daniel Murphy wrote:
>>>> "Steven Schveighoffer"  wrote in message news:op.xcnu55j2eav7ka@stevens-macbook-pro.local...
>>>>
>>>>> > The worst breaking change in D2, by far, is the prevention
>>>>> > > of
>>>>> array stomping.
>>>>>
>>>>> What is your use case(s), might I ask? Prevention of array stomping, I thought, had a net positive effect on performance, because it no longer has to lock the GC for thread-local appends.
>>>>
>>>> I would guess they're setting length to zero and appending to re-use the memory.
>>>
>>> Exactly. So far looks like upon transition to D2 almost all arrays used in our code will need to be replaced with some variation of Appender!T
>>
>> I think you might find that it will run considerably faster in that case. In the old mechanism of D1, the GC lock was used on every append, and if you had multiple threads appending simultaneously, they were contending with the single element cache to look up block info. Appender only needs to look up GC block info when it needs more memory from the GC.
>
> We don't use threads.

In that case, the GC will not use a lock (I think it avoids allocating a lock until at least 2 threads exist). However, you can still benefit from the expanded cache if you are appending to 2 to 8 arrays simultaneously. The D1 cache was for one block only.

-Steve
March 14, 2014
On Fri, 14 Mar 2014 06:06:41 -0400, Don <x@nospam.com> wrote:

> On Thursday, 13 March 2014 at 19:28:59 UTC, Walter Bright wrote:
>> On 3/13/2014 1:43 AM, Don wrote:
>>> The worst breaking change in D2, by far, is the prevention of array stomping.
>>>
>>> After that change, our code still runs, and produces exactly the same results,
>>> but it is so slow that it's completely unusable. This one of the main reasons
>>> we're still using D1.
>>
>> I didn't know this. I'd like more details - perhaps I can help with how to deal with it.
>
> Our entire codebase assumes that stomping will happen. Simplest example:
>
> T[] dupArray(T)(ref T[] dest, T[] src)
> {
>      dest.length = src.length;
>      if (src.length) {
>          dest[] = src[];
>      }
>      return dest;
> }

OK, thanks. This is not the same as setting length to 0. You would have to re-introduce stomping completely to fix this without modification, and that will break too much library code. My proposed idea is not going to help here.

Fixing this function involves adding assumeSafeAppend:

T[] dupArray(T)(ref T[] dest, T[] src)
{
   dest.length = src.length;
   dest.assumeSafeAppend();
   ... // the rest is the same.
}

> This is equivalent to dest = src.dup, but if dest was already long enough to contain src, no allocation occurs.
>
> Sure, we can add a call to "assumeSafeAppend()" everywhere. And I mean *everywhere*. Every single instance of array creation or concatentation, without exception. Almost every array in our codebase is affected by this.

Only array shrinking. Concatenation always allocates. Doing it after append is redundant. Doing it after creation is redundant.

Doing it *before* appending is possibly proactive, but I think just doing it whenever the length might shrink is good enough.

I think it may be best to introduce a new array property:

dest.slength = src.length; // same as dest.length = src.length, but follows D1 rules (slength = stomp length)

If you use slicing (I'm assuming you do), then appending would have to become a function/member instead of ~=.

I can help write those if you want.

-Steve
March 14, 2014
Steven Schveighoffer:

> I think it may be best to introduce a new array property:
>
> dest.slength = src.length; // same as dest.length = src.length, but follows D1 rules (slength = stomp length)
>
> If you use slicing (I'm assuming you do), then appending would have to become a function/member instead of ~=.
>
> I can help write those if you want.

Eventually the D1 legacy will go away, so probably it's better to introduce something that later can be removed safely. So instead of "slength" it's better a name like "__slength", to allow its usage only if you compile with the "-d" compile switch (that activates deprecated features) and instead of a built-in array property as "length" it could be better (if possible) to make it a function in the object module as assumeSafeAppend.

Bye,
bearophile
March 14, 2014
On Thu, 13 Mar 2014 18:55:26 -0400, Timon Gehr <timon.gehr@gmx.ch> wrote:

> On 03/13/2014 11:43 PM, Walter Bright wrote:
>>>
>>> version(A) version=AorB;
>>> version(B) version=AorB;
>>> version(AorB){ }
>>>
>>
>> If you're writing things like that, it's still missing the point. ...
>
> Unless the point is to demonstrate that it is not too convoluted. ;)

And for that point, your demonstration has failed :) The above is very convoluted, not very DRY.

-Steve
March 14, 2014
On Friday, 14 March 2014 at 14:02:06 UTC, Steven Schveighoffer
wrote:
>
> And for that point, your demonstration has failed :)

It's an extraordinarily simple use case, but it is still quite a
common pattern in C++ defines, ie:

#ifdef _DEBUG
#define FEATUREA
#define FEATUREB
#define FEATUREC
#else
#define FEATUREB
#define FEATUREC
#endif

#ifdef FEATUREB
...
#endif

In D, that would look a lot like:

debug
{
version = FeatureA;
version = FeatureB;
version = FeatureC;
}
else
{
version = FeatureB;
version = FeatureC;
}

version(FeatureB)
{
...
}

So for an actual use case and not an abstract example, yes, the
way suggested is not convoluted at all.
March 14, 2014
On Friday, 14 March 2014 at 06:30:55 UTC, Manu wrote:
> Please don't consider !final, that's like pouring lemon juice on the would.
> Use virtual, it's a keyword that everybody knows and expects, and already
> exists in peoples code that they might be migrating to D.

!final scale much better, as it is consistent pattern that can be used for any attribute. I like it much more than virtual.

Sounds weird though. Having `virtual` and `!virtual` would have better option but is not feasible.
March 14, 2014
On Thu, 13 Mar 2014 18:43:36 -0400, Walter Bright <newshound2@digitalmars.com> wrote:

> On 3/13/2014 3:21 PM, Timon Gehr wrote:
>> On 03/13/2014 02:32 PM, Steven Schveighoffer wrote:
>>>
>>> The one I would really like to see is logical OR. There is no easy way
>>> around this, one must come up with convoluted mechanisms that are much
>>> harder to design, write, and understand than just version(x || y)
>>
>> version(A) version=AorB;
>> version(B) version=AorB;
>> version(AorB){ }
>>
>
> If you're writing things like that, it's still missing the point. The point is not to find workarounds, but to rethink just what feature is being version'd on.

There are some times where AorB is the best description, and the machinery to factor out is just unnecessarily verbose.

I'm not denying that in some cases, when each version requires it's own unique block (Andrei's is a good example) that avoiding OR expressions makes a lot more sense.

But, those are not all cases. For example, if you have 4 different versions, and 3 of the blocks are the same in module x:

version(a)
{
   decla;
   declb;
   declc;
}
version(b)
{
   decla;
   declb;
   declc;
}
version(c)
{
   decla;
   declb;
   declc;
}
version(d)
{
   decld;
   decle;
   declf;
}
else
   assert(0, "unsupported version!");

Factoring this out becomes an exercise in treasure-map reading:

version(blahblahblah)
{
   decla;
   declb;
   declc;
}
version(d)
{
   decld;
   decle;
   declf;
}
else
  assert(0, "unsupported version!");

Now, I have to go find blahblahblah. Maybe there's even another factoring, and blahblahblah is defined by another intermediate version.

Maybe in another block, I have to use yaddayaddayadda, because only a and b use a common block and c is different.

Granted, I can invent names for these that make sense, and in some cases (like version(posix) or version(unixen) or something) it makes a lot of sense that anything which supports them will go into that version. I get that.

But when I'm looking at something like blahblahblah, and wondering if it's compiled with version(a), I have to go on a hunt. Why not just:

version(a || b || c)

In fact, it probably is a good idea to do:

version(blahblahblah) // a || b || c

to be clearer...

Note that it's already in your best interest to factor a || b || c into another identifier, instead of having to repeat that over and over again. But sometimes, it's just unnecessarily verbose hoops you have to jump through when reading or writing versioned code.

Also note that many many times, a, b, and c are mutually exclusive. So there will only be OR clauses for those, never AND. The code is readable and straightforward with boolean operators, convoluted and verbose without.

In any case, I don't expect any movement on this. Just had to rant ;)

-Steve