February 12, 2013
On Tuesday, 12 February 2013 at 15:53:57 UTC, Namespace wrote:
> Don't get me wrong, I also hope that this unfortunate and lengthy discussion ends. And I also like the idea that const ref works as in C++.
> But I'm sure you can convince neither Walter nor Andrei still the core developer team.

I tried my best a while back, but they sadly didn't take part in the discussion. Just in case, it's summarized in http://forum.dlang.org/thread/zteryxwxyngvyqvukqkm@forum.dlang.org.
I could live with 'const auto ref' too, but I'd hate having to type these 5 additional characters every time. ;)

> And I don't quite understand what speaks against my suggestion.
> If the compiler decides that by value is a better solution, then structs are received by value, otherwise by ref. But the user don't need to worry about it, because, whatever the compiler may decide to do, he adapt calls to these functions automatically.

Your proposal, if I understood correctly, restricts deadalnix' approach to rvalues only and additionally requires changing the param from 'A' to 'A&'. But iirc rvalues are already passed directly (moved) in D, i.e., they are not copied - so your approach is 1) simply not needed and 2) would require the A -> 'A&' transform (similar to A -> 'auto ref A').

> It is in principle nothing more than what you want.

No, nothing more, but it's already covered by D, without special syntax. I'd like to see
1) the move optimization applied to safe lvalue cases as well, also without requiring syntactic changes;
2) Kenji's 'auto ref' proposal getting pulled in the near future so that we can manually prevent argument copying for the remaining unsafe lvalue cases.
February 12, 2013
> 2) Kenji's 'auto ref' proposal getting pulled in the near future so that we can manually prevent argument copying for the remaining unsafe lvalue cases.

Maybe in 2065 - if we're lucky. ;)
Read also in my thread that I had posted earlier: auto ref - again.
There are also one of the rare responses of Andrei to this topic.
This problem isn't solved any time soon.
February 12, 2013
> Maybe in 2065 - if we're lucky. ;)

I meant of course dmd version 2.065, not the year 2065. ;)
February 12, 2013
On Monday, 11 February 2013 at 18:38:28 UTC, John Colvin wrote:
> On Monday, 11 February 2013 at 17:51:30 UTC, deadalnix wrote:
>> Finally, why would you disable something that make your code faster ?
>
> in order to do easier debugging / to avoid implementation bugs

This is where the difference between allowing and enforcing lies.
February 12, 2013
On Monday, 11 February 2013 at 22:40:44 UTC, Dan wrote:
> On Monday, 11 February 2013 at 16:56:38 UTC, deadalnix wrote:
>> On Monday, 11 February 2013 at 16:51:22 UTC, Steven Schveighoffer wrote:
>>> On Mon, 11 Feb 2013 10:08:52 -0500, deadalnix <deadalnix@gmail.com> wrote:
>>>
>>>> A good rule of thumb to know when to pass by ref for perf is :
>>>> - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience.
>>>
>>> Array slices are 2*sizeof(size_t).  I would expect them to be always copied and not ref'd.
>>>
>>
>> First, they alway appears to be copied from the dev perspective. That why I put bunch of restrictions in the proposal.
>>
>> Second, slice are 2*size_t and are not mixed entities (from CPU perspective, pointer are integers). So I don't have numbers, but I expect slice to be faster when passed by copy than when passed by ref.
>
> The idea of compiler choosing the optimal between: 'T t' and 'ref const(T) t' has been brought up a few times. Here is one attempt at getting numbers to see where a cutoff might be.
>
> http://forum.dlang.org/thread/opufykfxwkkjchqcwgrg@forum.dlang.org
>
> Based on this, and to avoid the boilerplate of read accessors, I use the following heuristic. If others have more friendly ways I'd be interested.
>
> Thanks
> Dan
>
>
> /** Discriminates a pass type by its size
>  */
> template PrefersPassByRef(T) {
>   static if(isAssociativeArray!T || isDynamicArray!T) {
>     enum PrefersPassByRef = false;
>   } else static if(T.sizeof > 16 || hasAliasing!T)  {
>     enum PrefersPassByRef = true;
>   } else {
>     enum PrefersPassByRef = false;
>   }
> }
>
> /** Discriminates a pass type by its size
>  */
> template PreferredPassType(T) {
>   static if(PrefersPassByRef!T) {
>     enum PreferredPassType = `const ref `~T.stringof;
>   } else {
>     enum PreferredPassType = T.stringof;
>   }
> }
>
> /** Provides mixin for making a field read only.
>  *  For example mixin(ReadOnly!_fieldName) provides a getter named fieldName.
>  */
> template ReadOnly(alias name) {
>   enum v = name.stringof;
>   enum p = name.stringof[1..$];
>   enum prefersReference = PrefersPassByRef!(typeof(name));
>   static if(prefersReference) {
>     mixin(`
> public @property auto ref `~p~`() const {
>   return `~v~`;
> }
> `);
>   } else {
>     mixin(`
> public @property auto `~p~`() const {
>   return `~v~`;
> }
> `);
>   }
> }

This is a lot o code and don't handle some cases. For instance

struct A {
    long a;
    double b;
}

is generally better passed by ref, where

struct A {
    long a;
    long b;
}

is better passed by value.
February 12, 2013
On Monday, 11 February 2013 at 23:16:55 UTC, Peter Alexander wrote:
> On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:
>> I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain.
>
> struct Foo { int x, y; }
>
> class Bar
> {
>     Foo[] foos = [Foo(1, 2)];
>
>     void swap(const(Foo) f)
>     {
>         foos[0].x = f.y;
>         foos[0].y = f.x;
>     }
> }
>
> void quux(Bar bar, const(Foo) foo)
> {
>     bar.swap(foo);
> }
>
> Are the foo parameters allowed to be passed by ref in both these functions? I see no reason why not from your criteria, but doing so changes the program.

I guess the constraint are too lax. Is it possible to get around that without creating a cascade of special cases ? I frankly don't know, I have to dig more into this.
February 12, 2013
On Tuesday, 12 February 2013 at 17:33:08 UTC, deadalnix wrote:
> This is a lot o code

Fortunately, like a lot of code you don't need to look at it to use it. That is the purpose of it.

  struct ReductionMap(K, V, alias reduction_op = "+", alias init = 0) {
    mixin ReadOnly!_dataMap;
    mixin ReadOnly!_reduction;
    alias V[K] DataMap;
    ...
    private {
      DataMap _dataMap;
      V _reduction = init;
    }
  }


> and don't handle some cases. For instance
>

I did not say it handled everything and if you have strategies that will allow me to simply, declaratively get a better selection between the two pass types I'd incorporate it. If and until the language changes, what other choices are there?

I just used the numbers to come up with a heuristic, which I imagine, given the state of things is how any one makes the choice. The only difference is the choice is somewhat formalized.


> struct A {
>     long a;
>     double b;
> }
>
> is generally better passed by ref, where
>
> struct A {
>     long a;
>     long b;
> }
>
> is better passed by value.

Any performance numbers?

Even if that were true, is it realistic to expect a compiler to get it right each time?

Thanks
Dan

February 12, 2013
On Tuesday, 12 February 2013 at 13:34:10 UTC, kinke wrote:
> I think the intended move semantics (byval passing for small structs w/o postblit constructor/destructor, otherwise byref) are only really safe if the argument is an rvalue - the rvalue is guaranteed not to be used after the call, so potential modifications are not visible for the caller, and the rvalue is guaranteed not to be used simultaneously in another thread. Certain lvalue cases could be optimized as well, e.g., if the lvalue is a private variable of the caller (local variable or parameter) AND is not used after the call.

Sorry, please discard these lvalue cases, they aren't safe, i.e., analog to my earlier example:

---
struct BigStruct { long a, b, c; }

long foo(const BigStruct s, ref long c)
{ // s is the callee's copy of the argument
  c = s.a + s.b + s.c;
  return s.c;
}
long foo_ref(const ref BigStruct s, ref long c)
{ // s is a reference to the caller's argument
  c = s.a + s.b + s.c;
  return s.c;
}

BigStruct s = { 1, 2, 3 };
long r1 = foo(s, s.c);
assert(s.c == 6 && r1 == 3); // returns callee's s.c
s.c = 3; // reset
long r2 == foo_ref(s, s.c); // returns caller's s.c
assert(s.c == 6 && r2 == 6);
---

So aliasing issues (the caller's argument is modified indirectly via another parameter/global variable) are able to break code when passing _any_ lvalue transparently byref, even if it's not used afterwards. So only rvalues are really safe for move semantics, i.e., they can't be modified by another thread or indirectly by the callee via aliasing AND modifications are not visible to the caller after the call.
February 12, 2013
On 02/11/2013 07:52 AM, deadalnix wrote:
> Ok, We have 2 usages of ref : when you actually need to modify
> informations, and for performance reasons. Let's talk about the second one.
>
> Passing by ref to improve performance is not ideal. First this is quite
> hard to know when it is actually faster to pass by ref and to pass by
> value, especially in generic code. Secondly it is easy to forget to use
> ref at some location, and a lot of small performance improvement are
> lost in the process. Finally, this may be error prone.
>
> I'm thinking about it for a while now and I'm now convinced that we
> should allow the compiler to do that job for us. Let me explain.
>
> When a function accept a struct, the compiler is free to use that
> function, or an altered one taking a reference as parameter. Here are
> some rules the compiler can use to know which one to call from callee
> side :
>
> The caller is free to call the ref version of the function unless (rules
> evaluate in order) :
>   - The argument is an rvalue (in such case, no postblit is executed as
> well).
>   - The argument is shared.
>   - The argument's postblit in not pure (weakly).
>
> The callee isn't modified for the vanilla function, but must create a
> local copy of the argument in the following reasons in the ref version :
>   - The argument is binded to a mutable ref.(even as hidden argument as
> for member method).
>   - The argument is actually modified.
>   - address of anything coming from the struct is taken.
>
> The compiler is free to apply such treatment only in a branch of the
> callee, ie :
>
> // Compiler choose to create an alternative ref version for performance.
> void foo(MyStruct s) {
>      if(condition) {
>          // Operation require a copy to not alter what the caller see.
>          // A s is copied on stack and postblit is called.
>          s.field = 5;
>      } else {
>          // No operation requiring local copy is performed.
>          // No local copy is created.
>      }
> }
>
> The compiler is however disallowed to create multiple copies in the
> callee. If several branches requires it, then the copy have to be made
> in a common branch.
>
> Note that the compiler don't HAVE to do this, but is allowed to.
> Modifying the spec in such way allow the compiler to avoid many copies
> of struct let us get rid of most ref parameters, keeping them for what
> they really are for.

You can easily create a function that turns values into rvalues if it's opportune.

template isBetterToPassAsValue(T)
{
  enum isBetterToPassAsValue = ...;
}

T cheapPass(T)(ref T t) if (isBetterToPassAsValue!T)
{
  return t;
}

ref T cheapPass(T)(ref T t) if (!isBetterToPassAsValue!T)
{
  return t;
}

T cheapPass(T)(T t) if (isBetterToPassAsValue!T)
{
  return move(t);
}

void foo(T)(auto ref T t)
{
  // ...
}

void bar()
{
  S1 s1;
  foo(cheapPass(s1)); // passed by ref
  S2 s2;
  foo(cheapPass(s2)); // passed by value
}

February 12, 2013
On Tuesday, 12 February 2013 at 17:29:07 UTC, deadalnix wrote:
> On Monday, 11 February 2013 at 18:38:28 UTC, John Colvin wrote:
>> On Monday, 11 February 2013 at 17:51:30 UTC, deadalnix wrote:
>>> Finally, why would you disable something that make your code faster ?
>>
>> in order to do easier debugging / to avoid implementation bugs
>
> This is where the difference between allowing and enforcing lies.

Of course, but it's always nice to be able to disable any optimization globally for any given compilation.

I personally like gcc's approach where you have very fine grained control over all optimisations. Something like this should have a compiler switch to enable/disable.