www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - About ref used for performance reasons with struct

reply "deadalnix" <deadalnix gmail.com> writes:
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.
Feb 10 2013
next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 06:52:33 UTC, 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.
EDIT: I forgot one condition for the callee, it is disallowed to copy the struct or any part of it that contain reference/pointer is the postblit isn't strongly pure.
Feb 10 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
But what if we want to pass a struct with intent as value?
With this solution, we don't have much control.
In general, I like the idea, but we should mark such parameter
with '&' or whatever, so at least we can still take some 
influence.
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 10:04:55 UTC, Namespace wrote:
 But what if we want to pass a struct with intent as value?
 With this solution, we don't have much control.
 In general, I like the idea, but we should mark such parameter
 with '&' or whatever, so at least we can still take some 
 influence.
That is the whole point, everything appear as if it is passed by value. struct A { uint member; this(this) { // Very long but pure operation. } } void main() { A a; foo(a); assert(a.member = 0); // Pass. } void foo(A a) { // Compiler can choose to pass by ref here. bar(a); } void bar(A a) { // If compiler choose to pass by ref, then it is bar responsibility to create a copy. A smarter move from the compiler is to mark bar as non electable for such optimization. a.member = 5; } In the example above, the compiler is allowed to pass a by ref to foo. As a consequence, the very long operation within the postblit can only be executed once, instead of 2.
Feb 11 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
On Monday, 11 February 2013 at 10:53:40 UTC, deadalnix wrote:
 On Monday, 11 February 2013 at 10:04:55 UTC, Namespace wrote:
 But what if we want to pass a struct with intent as value?
 With this solution, we don't have much control.
 In general, I like the idea, but we should mark such parameter
 with '&' or whatever, so at least we can still take some 
 influence.
That is the whole point, everything appear as if it is passed by value. struct A { uint member; this(this) { // Very long but pure operation. } } void main() { A a; foo(a); assert(a.member = 0); // Pass. } void foo(A a) { // Compiler can choose to pass by ref here. bar(a); } void bar(A a) { // If compiler choose to pass by ref, then it is bar responsibility to create a copy. A smarter move from the compiler is to mark bar as non electable for such optimization. a.member = 5; } In the example above, the compiler is allowed to pass a by ref to foo. As a consequence, the very long operation within the postblit can only be executed once, instead of 2.
I see. Nice idea. But what is the criterion for the compiler to pass 'a' by ref? I'm still therefor, to mark such paramters with '&' or something but that's maybe my C++ background. I'm curious to see if Walter or Andrei say something to the idea.
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 12:16:14 UTC, Namespace wrote:
 I see. Nice idea. But what is the criterion for the compiler to 
 pass 'a' by ref?
 I'm still therefor, to mark such paramters with '&' or 
 something but that's maybe my C++ background.
 I'm curious to see if Walter or Andrei say something to the 
 idea.
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. - The struct have a postblit. The compiler doing it is superior to the user marking everything : - The user may forget, getting slow down for nothing. - The user can get it wrong the other way around (pass by ref when it isn't appropriate). - This is really hard to get right in generic code. - This is relatively easy to automate. The obvious drawback is possible code bloat, but that isn't that bad (and can be used as heuristic by the compiler to decide if it should pass by ref or not).
Feb 11 2013
next sibling parent reply "Namespace" <rswhite4 googlemail.com> writes:
 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.
  - The struct have a postblit.

 The compiler doing it is superior to the user marking 
 everything :
  - The user may forget, getting slow down for nothing.
  - The user can get it wrong the other way around (pass by ref 
 when it isn't appropriate).
  - This is really hard to get right in generic code.
  - This is relatively easy to automate.
I thought of something like this: void foo(A& a) { // the compiler choose if A should pass by ref or by value. } void bar(A a) { // normal behaviour. } That is identical with 'inline' in C++. You _can_ declare a function as inline, but finally the compiler choose if he really inline the function or not.
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 16:27:29 UTC, Namespace wrote:
 That is identical with 'inline' in C++. You _can_ declare a 
 function as inline, but finally the compiler choose if he 
 really inline the function or not.
Compiler now inline even when inline isn't present, and don't inline when inline is present but this don't make sense. In other terms, inline is the perfect example of why defining stuff like that explicitly make no sense.
Feb 11 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
 Compiler now inline even when inline isn't present, and don't 
 inline when inline is present [...]
That's what I said.
 In other terms, inline is the perfect example of why defining 
 stuff like that explicitly make no sense.
That was no criticism. I like to explicitly mark such things, but whatever, I like the idea anyway. I still hope for a response from Walter or Andrei.
Feb 11 2013
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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. -Steve
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
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.
Feb 11 2013
parent reply "Dan" <dbdavidson yahoo.com> writes:
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~`; } `); } }
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
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.
Feb 12 2013
parent "Dan" <dbdavidson yahoo.com> writes:
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
Feb 12 2013
prev sibling parent "Namespace" <rswhite4 googlemail.com> writes:
How about this approach:
You almost need functions, which takes structs as an rvalue, for 
things like:
----
struct A { }
void foo(A a) { }

foo(A());
----
So what's about something like this (Yes I mark it with '&'):
----
struct A { }
void foo(A& a) { }
----
The compiler decides at compile time whether A should be taken by 
ref or by value.
So for example, if A.sizeof > 16, it changes to:
----
void foo(ref A a) {
----
otherwise:
----
void foo(A a) {
----
In the latter case nothing happens for calls like: foo(A());
But in the former case the call:
foo(A());
is changed to:
auto __temp = A(); foo(__temp);
Feb 11 2013
prev sibling next sibling parent reply "Paulo Pinto" <pjmlp progtools.org> writes:
On Monday, 11 February 2013 at 06:52:33 UTC, 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.
How does this work in the context of modular programming? Does the compiler generate multiple code for each case, to make the code work regardless of what is decided at the call site? Or is the code written in a canonical form inside the module, which gets to be rewritten when all modules are linked into the final binary? By modules I mean the case where you only have a .di file + binary code for the module. For me the right form to distribute libraries. -- Paulo
Feb 11 2013
parent "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 10:21:30 UTC, Paulo Pinto wrote:
 How does this work in the context of modular programming?

 Does the compiler generate multiple code for each case, to make 
 the code work regardless of what is decided at the call site?

 Or is the code written in a canonical form inside the module, 
 which gets to be rewritten when all modules are linked into the 
 final binary?

 By modules I mean the case where you only have a .di file + 
 binary code for the module. For me the right form to distribute 
 libraries.
I think it is reasonable to say that this will not work is the compiler don't have callee's implementation.
Feb 11 2013
prev sibling next sibling parent "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Monday, 11 February 2013 at 06:52:33 UTC, 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.
I touched on the idea in a StackOverflow answer a while back: http://stackoverflow.com/a/8515844 The meaning of 'in' means such parameters can in theory be optimized if the compiler has the proper sources, so it would seem right to leverage it.
Feb 11 2013
prev sibling next sibling parent "Minas Mina" <minas_mina1990 hotmail.co.uk> writes:
On Monday, 11 February 2013 at 06:52:33 UTC, 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.
+1 It's really ugly to write "const ref Blah blah" all the time when the compiler could it.
Feb 11 2013
prev sibling next sibling parent reply "kinke" <noone hotmail.com> writes:
+1, I've been thinking about similar concepts as well.

This would be particularly useful for Win64. The Win64 ABI 
requires all structs > 64 bits (or of a size which is not a power 
of 2) to be passed by reference. So if a function foo(BigStruct 
s) is invoked, the caller first allocates a copy of s for the 
callee on its own stack (16-bytes aligned) and then passes the 
callee a pointer to the copy (in a register or on the parameters 
stack), instead of passing a copy of s directly on the parameters 
stack. It seems to me that the whole idea of this is to encourage 
byref passing and copying arguments only if required, otherwise 
it doesn't make much sense performance-wise (higher stack memory 
usage due to the additional pointer and need for dereferencing 
the pointer parameter).

I'd propose a small change so that suited structs are passed 
transparently byref only if the parameter is not mutable, e.g., 
for a function foo(const BigStruct s). The compiler would 
therefore not need to analyze the code flow in the callee to 
determine if the parameter is modified and hence a copy is needed.

The compiler would nevertheless need to be quite smart though:

---
struct MyBigStruct { double a, b, c; }
double foo(const MyBigStruct s, double* x)
{
     *x = s.a;
     return s.b;
}
// naive optimization by byref passing
double foo_ref(const ref MyBigStruct s, double* x)
{
     *x = s.a;
     return s.b;
}

MyBigStruct s = { 1, 2, 3 };
double* x = &s.b;
auto bla = foo(s, x); // returns 2; now s.b = 1
s.b = 2;              // reset s.b to 2
bla = foo_ref(s, x);  // returns 1! (the new s.b = 1)
---

So the compiler would need to prove there is no way the argument 
can be modified and handle tricky aliasing issues accordingly.
Feb 11 2013
next sibling parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:
 I'd propose a small change so that suited structs are passed 
 transparently byref only if the parameter is not mutable, e.g., 
 for a function foo(const BigStruct s). The compiler would 
 therefore not need to analyze the code flow in the callee to 
 determine if the parameter is modified and hence a copy is 
 needed.
That is overly restrictive. See sample code given in this thread, foo couldn't get the struct by ref in such a case if A contains any indirection.
Feb 11 2013
parent "kinke" <noone hotmail.com> writes:
On Monday, 11 February 2013 at 15:12:07 UTC, deadalnix wrote:
 On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:
 I'd propose a small change so that suited structs are passed 
 transparently byref only if the parameter is not mutable, 
 e.g., for a function foo(const BigStruct s). The compiler 
 would therefore not need to analyze the code flow in the 
 callee to determine if the parameter is modified and hence a 
 copy is needed.
That is overly restrictive. See sample code given in this thread, foo couldn't get the struct by ref in such a case if A contains any indirection.
Not sure what you mean by A containing any indirections, but I'd simply rewrite your example as follows: void foo(const A a) { // byref passing desirable bar(a); // bar's parameter is mutable => pass a copy (byval) } void bar(A a) { // byval passing a.member = 5; }
Feb 11 2013
prev sibling parent reply "kinke" <noone hotmail.com> writes:
On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:
 I'd propose a small change so that suited structs are passed 
 transparently byref only if the parameter is not mutable, e.g., 
 for a function foo(const BigStruct s). The compiler would 
 therefore not need to analyze the code flow in the callee to 
 determine if the parameter is modified and hence a copy is 
 needed.

 The compiler would nevertheless need to be quite smart though:

 ---
 struct MyBigStruct { double a, b, c; }
 double foo(const MyBigStruct s, double* x)
 {
     *x = s.a;
     return s.b;
 }
 // naive optimization by byref passing
 double foo_ref(const ref MyBigStruct s, double* x)
 {
     *x = s.a;
     return s.b;
 }

 MyBigStruct s = { 1, 2, 3 };
 double* x = &s.b;
 auto bla = foo(s, x); // returns 2; now s.b = 1
 s.b = 2;              // reset s.b to 2
 bla = foo_ref(s, x);  // returns 1! (the new s.b = 1)
 ---

 So the compiler would need to prove there is no way the 
 argument can be modified and handle tricky aliasing issues 
 accordingly.
Thinking about it some more, I'm fairly convinced the proposal in this thread is just too dangerous. Proving the argument passed transparently byref cannot be modified is just too complex imo; e.g., it could be modified by the callee via aliasing issues shown in the example above, or it could be modified by another thread while the callee is running, hence modifying the callee's parameter at the same time! 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. --- struct MyBigStruct { double a, b, c; } double bar(MyBigStruct s) { return s.a; } double foo(MyBigStruct s) // s is an lvalue (parameter) { return bar(s); // s NOT used afterwards => byref passing } void main() { MyBigStruct s; // s is an lvalue (local variable) invoke foo(s) in another thread; // s used afterwards => byval if (...) { foo(s); // s used afterwards (next line) => byval s.a += 1; } else foo(s); // s NOT used afterwards => byref } --- So imo parameters need to be denoted by something special like 'auto ref' if a copy is to be elided for performance reasons - not in its current form though (only for templates and leading to code-bloating); instead, rvalues should simply be transformed to lvalues before the call and then passed byref just like ordinary lvalues. And as I've already pointed out a few times in recent discussions, I'm not a fan of 'const auto ref' for not-mutable parameters, so I'd very much like to see 'const ref' for these (allowing rvalues too, just like C++). The function signature therefore clearly indicates that these params are references to the caller's arguments, with all potentially dangerous implications.
Feb 12 2013
next sibling parent reply "Namespace" <rswhite4 googlemail.com> writes:
Read my approach. I suggest something like A& a. It's short and 
known from C++.
Only in my approach, I suggest a link between the proposal of 
deadalnix (compiler optimizations) and generally rvalue 
references.
'const ref' will never work the way as we know it from C++. 
Walter and Andrei and many others are totally against it.
However, 'auto ref' is also not a real solution, because 'auto 
ref' generates 2^(n - 1) permuationen of the same function - code 
bloat.
'auto ref' accept of course lvalues and rvalues but you gain no 
performance.
Feb 12 2013
parent reply "kinke" <noone hotmail.com> writes:
On Tuesday, 12 February 2013 at 13:59:45 UTC, Namespace wrote:
 Read my approach. I suggest something like A& a. It's short and 
 known from C++.
... where A& stands for a reference to an A instance. But in your approach it denotes either a plain A instance OR a reference to it, so I don't like your suggestion at all, I'm afraid. Additionally, afaik D/DMD (?) already implements move semantics for rvalues, so 1) the goal of your approach is already implemented and 2) the existing optimization doesn't require any special parameter denotation (plain A is fine).
 Only in my approach, I suggest a link between the proposal of 
 deadalnix (compiler optimizations) and generally rvalue 
 references.
Yes, and I'd extend it for the illustrated lvalue cases as well.
 'const ref' will never work the way as we know it from C++. 
 Walter and Andrei and many others are totally against it.
And there are many others totally in favour of it. ;) I'm still waiting for a plausible argument as to why the callee needs to know whether a passed const reference actually references an lvalue or an rvalue. I'm tired of this const ref discussion, and probably many others are too.
 However, 'auto ref' is also not a real solution, because 'auto 
 ref' generates 2^(n - 1) permuationen of the same function - 
 code bloat.
That's what I said basically regarding the current 'auto ref' implementation.
 'auto ref' accept of course lvalues and rvalues but you gain no 
 performance.
Of course you'd gain performance because lvalues would not be copied (just like 'ref' only). But in contrast to 'ref', you'd also be able to pass rvalues directly (byref), without having to overload the function or turn the rvalue manually to an lvalue right before the call. The thing is that with 'auto ref', you have to determine if the struct is suited for copy-elision and then decorate the params manually with 'auto ref'/'const auto ref'/'const ref' ;). deadalnix listed some good reasons why the compiler should do that for us. But I think I also mentioned good reasons as to why the compiler most likely can't due to the huge complexity involved to make sure code doesn't break. Although it is unlikely to break in most cases, an optimization feature such as this needs to work for ALL cases. This is why I suggested applying deadalnix' intended implicit move semantics only for rvalues and the mentioned, safe lvalue cases. Afaik D already implements move semantics for rvalues, but I guess it doesn't optimize the lvalue cases. For all other cases, I think we sadly need to resort to a revisited 'auto ref' approach.
Feb 12 2013
next sibling parent reply "Namespace" <rswhite4 googlemail.com> writes:
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.

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. It 
is in principle nothing more than what you want.
Feb 12 2013
parent reply "kinke" <noone hotmail.com> writes:
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.
Feb 12 2013
parent reply "Namespace" <rswhite4 googlemail.com> writes:
 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.
Feb 12 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
 Maybe in 2065 - if we're lucky. ;)
I meant of course dmd version 2.065, not the year 2065. ;)
Feb 12 2013
prev sibling parent reply "kinke" <noone hotmail.com> writes:
 For all other cases, I think we sadly need to resort to
 a revisited 'auto ref' approach.
I.e., something like this: https://github.com/D-Programming-Language/dmd/pull/1019
Feb 12 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
On Tuesday, 12 February 2013 at 15:57:42 UTC, kinke wrote:
 For all other cases, I think we sadly need to resort to
 a revisited 'auto ref' approach.
I.e., something like this: https://github.com/D-Programming-Language/dmd/pull/1019
http://forum.dlang.org/thread/nirfuenixutsbgyrcsla forum.dlang.org ;)
Feb 12 2013
prev sibling parent "kinke" <noone hotmail.com> writes:
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.
Feb 12 2013
prev sibling next sibling parent reply Michel Fortin <michel.fortin michelf.ca> writes:
On 2013-02-11 06:52:28 +0000, "deadalnix" <deadalnix gmail.com> said:

 When a function accept a struct, the compiler is free to use that 
 function, or an altered one taking a reference as parameter.
When designing the ABI, you can either create the copy on the caller's side, or the callee's side. If I'm understanding well, you want to have two versions of each function, one copying on the caller's side, one copying on the callee's side, and the caller's codegen would choose which one to use. Copying on the caller's side is better if you're passing rvalues (because the caller can just always skip the copying), while copying on the callee's side is better when you're passing lvalues, because the caller may decide to skip copying if it doesn't need a copy. I'm not sure creating two versions of each function will fly well with Walter. Actually, you say *two* are needed, but that's for a function with one struct parameter; it's more 2^(number of structs parameters) versions you'd need in the general case. -- Michel Fortin michel.fortin michelf.ca http://michelf.ca/
Feb 11 2013
next sibling parent "Namespace" <rswhite4 googlemail.com> writes:
 I'm not sure creating two versions of each function will fly 
 well with Walter. Actually, you say *two* are needed, but 
 that's for a function with one struct parameter; it's more 
 2^(number of structs parameters) versions you'd need in the 
 general case.
That's how 'auto ref' currently works. But I do not think that code bloat was the intention of deadalnix. Quite the contrary.
Feb 11 2013
prev sibling parent "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 15:48:32 UTC, Michel Fortin wrote:
 On 2013-02-11 06:52:28 +0000, "deadalnix" <deadalnix gmail.com> 
 said:

 When a function accept a struct, the compiler is free to use 
 that function, or an altered one taking a reference as 
 parameter.
When designing the ABI, you can either create the copy on the caller's side, or the callee's side. If I'm understanding well, you want to have two versions of each function, one copying on the caller's side, one copying on the callee's side,
That is blatantly wrong. You don't have, the compiler is allowed to, and the copy on callee side isn't mandatory.
Feb 11 2013
prev sibling next sibling parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Monday, 11 February 2013 at 06:52:33 UTC, 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 :
I expect this to be a nightmare for both users, who would experience new pile of struct bugs and for developers. How much code would this proposal break? What do you mean by 'altered'? Are you taking about two function versions or a single one? If first, how does it work with linking, and if second, how does it work for different situations? What happens when you use function pointer or a delegate? How __traits works with respect to function type in case of such optimization? What happens if compiler cannot deduce from context information required to make such decision? How does it play with variardic functions? And how such optimization can be disabled?
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
On Monday, 11 February 2013 at 17:15:40 UTC, Maxim Fomin wrote:
 On Monday, 11 February 2013 at 06:52:33 UTC, 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 :
I expect this to be a nightmare for both users, who would experience new pile of struct bugs and for developers. How much code would this proposal break?
None.
 What do you mean by 'altered'? Are you taking about two 
 function versions or a single one? If first, how does it work 
 with linking, and if second, how does it work for different 
 situations? What happens when you use function pointer or a 
 delegate? How __traits works with respect to function type in 
 case of such optimization? What happens if compiler cannot 
 deduce from context information required to make such decision? 
 How does it play with variardic functions? And how such 
 optimization can be disabled?
The optimization cannot be used on opaques calls. This include function pointer and alike. The only function that is guaranteed to exist is the one that take the struct by value. The other one is an option that the compiler is allowed to choose to make things faster. It is applicable for variadic function for non variadic argument, not for variadic arguments. Finally, why would you disable something that make your code faster ?
Feb 11 2013
next sibling parent reply "John Colvin" <john.loughran.colvin gmail.com> writes:
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
Feb 11 2013
parent reply "deadalnix" <deadalnix gmail.com> writes:
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.
Feb 12 2013
parent "John Colvin" <john.loughran.colvin gmail.com> writes:
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.
Feb 12 2013
prev sibling parent reply "Namespace" <rswhite4 googlemail.com> writes:
I'm a bit confused now. It's your intention to create (worst 
case) 2^n permutations of the same function for each potential 
ref parameter (which would be the same behaviour as 'auto ref') 
or that the compiler could pass big structs by ref without code 
bloat?
Feb 11 2013
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 11 February 2013 at 19:14:15 UTC, Namespace wrote:
 I'm a bit confused now. It's your intention to create (worst 
 case) 2^n permutations of the same function for each potential 
 ref parameter (which would be the same behavior as 'auto ref') 
 or that the compiler could pass big structs by ref without code 
 bloat?
For some reason I doubt that's what he means. Seems more often than not only one or two parameter(s) need to be ref-able, so in most cases with 1 variable two (and only two) functions be made and it choose between the two based on the result. Larger than that and another method would have to be selected.
Feb 11 2013
prev sibling next sibling parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On Mon, 11 Feb 2013 07:52:28 +0100
"deadalnix" <deadalnix gmail.com> 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.
 
To be honest, up until recently, I mistakenly thought the compiler DID do this. (Yea, my mistake.) Is it possible I had confused structs with static arrays? Do static arrays do that?
Feb 11 2013
next sibling parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 11 February 2013 at 21:45:11 UTC, Nick Sabalausky 
wrote:
 On Mon, 11 Feb 2013 07:52:28 +0100 "deadalnix" 
 <deadalnix gmail.com> 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.
 
To be honest, up until recently, I mistakenly thought the compiler DID do this. (Yea, my mistake.) Is it possible I had confused structs with static arrays? Do static arrays do that?
I'm pretty sure static/fixed arrays are more like structs, and thereby whole copies are made rather than sending a slice. Let's find out. void main() { char[4] test = "test"; writeln(&test, "- main"); fun(test); if (test == "test") writeln("is passed by value/copy"); else { assert(test == "best", "Something's wrong..."); writeln("is passed by slice/ref"); } } void fun(char[4] test){ assert(test == "test"); writeln(&test, "- fun"); test[0] = 'b'; assert(test == "best"); } Output: 18FDD0- main 18FDAC- fun is passed by value/copy
Feb 11 2013
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 11 February 2013 at 22:03:52 UTC, Era Scarecrow wrote:
 Output:

 18FDD0- main
 18FDAC- fun
 is passed by value/copy
Now had the function been 'void fun(char[] test)', then passing a static/fixed array would have errored and you'd be required to send a slice of it's contents, which then is by ref. Seems using the address doesn't seem to be very helpful, so for slices you'd refer to the ptr of the array. I'm sure there's other tests that can be done... to help with the expected behavior //appended to main fun2(test); if (test == "test") writeln("is passed by value/copy"); else { assert(test == "best", "Something's wrong..."); writeln("is passed by slice/ref"); } } void fun2(char[] test){ assert(test == "test"); //&test points to local test not it's contents writeln(test.ptr, "- fun2"); test[0] = 'b'; assert(test == "best"); } new Output: 18FDD0- main 18FDA4- fun is passed by value/copy 18FDD0- fun2 is passed by slice/ref
Feb 11 2013
prev sibling parent reply "jerro" <a a.com> writes:
 To be honest, up until recently, I mistakenly thought the 
 compiler DID
 do this. (Yea, my mistake.)

 Is it possible I had confused structs with static arrays? Do 
 static
 arrays do that?
Maybe you were thinking about named return value optimization (which applies to return values, not parameters)?
Feb 11 2013
parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 02/11/2013 04:31 PM, jerro wrote:
 To be honest, up until recently, I mistakenly thought the compiler DID
 do this. (Yea, my mistake.)

 Is it possible I had confused structs with static arrays? Do static
 arrays do that?
Maybe you were thinking about named return value optimization (which applies to return values, not parameters)?
There is a corresponding optimization for parameters, which can be applied only in some cases. The related guideline in C++ is this: If you are going to make a copy of the parameter inside the function anyway, take the parameter by-value. Here is a related article: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ Especially this part: "Also, although the compiler is normally required to make a copy when a function parameter is passed by value (so modifications to the parameter inside the function can’t affect the caller), it is allowed to elide the copy, and simply use the source object itself, when the source is an rvalue." Ali
Feb 11 2013
prev sibling next sibling parent reply "Peter Alexander" <peter.alexander.au gmail.com> writes:
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.
Feb 11 2013
parent "deadalnix" <deadalnix gmail.com> writes:
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.
Feb 12 2013
prev sibling parent reply Martin Nowak <code dawg.eu> writes:
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 }
Feb 12 2013
parent reply Martin Nowak <code dawg.eu> writes:
On 02/13/2013 12:34 AM, Martin Nowak wrote:
 You can easily create a function that turns values into rvalues if it's
 opportune.
Actually there is also a nice template based implementation of this in phobos (std.algorithm.forward). https://github.com/D-Programming-Language/phobos/blob/8bf6997acfdbe81442e4c3e4c3500b9197ffaaca/std/algorithm.d#L2002
Feb 12 2013
parent "Namespace" <rswhite4 googlemail.com> writes:
On Tuesday, 12 February 2013 at 23:49:04 UTC, Martin Nowak wrote:
 On 02/13/2013 12:34 AM, Martin Nowak wrote:
 You can easily create a function that turns values into 
 rvalues if it's
 opportune.
Actually there is also a nice template based implementation of this in phobos (std.algorithm.forward). https://github.com/D-Programming-Language/phobos/blob/8bf6997acfdbe81442e4c3e4c3500b9197ffaaca/std/algorithm.d#L2002
But that does not solve the problem. And it's not pleasant to use.
Feb 13 2013