Jump to page: 1 26  
Page
Thread overview
About ref used for performance reasons with struct
Feb 11, 2013
deadalnix
Feb 11, 2013
deadalnix
Feb 11, 2013
Namespace
Feb 11, 2013
deadalnix
Feb 11, 2013
Namespace
Feb 11, 2013
deadalnix
Feb 11, 2013
Namespace
Feb 11, 2013
deadalnix
Feb 11, 2013
Namespace
Feb 11, 2013
deadalnix
Feb 11, 2013
Dan
Feb 12, 2013
deadalnix
Feb 12, 2013
Dan
Feb 11, 2013
Namespace
Feb 11, 2013
Paulo Pinto
Feb 11, 2013
deadalnix
Feb 11, 2013
Jakob Ovrum
Feb 11, 2013
Minas Mina
Feb 11, 2013
kinke
Feb 11, 2013
deadalnix
Feb 11, 2013
kinke
Feb 12, 2013
kinke
Feb 12, 2013
Namespace
Feb 12, 2013
kinke
Feb 12, 2013
Namespace
Feb 12, 2013
kinke
Feb 12, 2013
Namespace
Feb 12, 2013
Namespace
Feb 12, 2013
kinke
Feb 12, 2013
Namespace
Feb 12, 2013
kinke
Feb 11, 2013
Michel Fortin
Feb 11, 2013
Namespace
Feb 11, 2013
deadalnix
Feb 11, 2013
Maxim Fomin
Feb 11, 2013
deadalnix
Feb 11, 2013
John Colvin
Feb 12, 2013
deadalnix
Feb 12, 2013
John Colvin
Feb 11, 2013
Namespace
Feb 11, 2013
Era Scarecrow
Feb 11, 2013
Nick Sabalausky
Feb 11, 2013
Era Scarecrow
Feb 11, 2013
Era Scarecrow
Feb 12, 2013
jerro
Feb 12, 2013
Ali Çehreli
Feb 11, 2013
Peter Alexander
Feb 12, 2013
deadalnix
Feb 12, 2013
Martin Nowak
Feb 12, 2013
Martin Nowak
Feb 13, 2013
Namespace
February 11, 2013
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.
February 11, 2013
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.
February 11, 2013
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.
February 11, 2013
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
February 11, 2013
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.
February 11, 2013
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.
February 11, 2013
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.
February 11, 2013
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.
February 11, 2013
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.
February 11, 2013
+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.
« First   ‹ Prev
1 2 3 4 5 6