www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - *in* vs *const ref*

reply "Dan" <dbdavidson yahoo.com> writes:
Googling to see differences between *in* and *const ref* I found 
detailed explanation here:
http://stackoverflow.com/questions/8515579/difference-between-const-ref-and-in

One response is: "If you have identified the copying as a 
bottleneck and you want to optimize, using *const ref* is a good 
idea."

Doesn't this imply there some other benefit to *in* - otherwise 
*const ref* would always be chosen?

Later on another response is: "A huge difference between *in* and 
*const ref* which you don't cover at all is the fact that *const 
ref* must take an lvalue, whereas *in* doesn't have to"

Why is this benefit huge? Is it just the convenience of being 
able to pass in literals or is it something more?

If I'm dealing with types T, where T is *not* a delegate, why not 
always just choose *const ref* and be done with it?
If the only downside is client code must declare variables where 
they would have a literal, is that such a big price?

Also, it does not mention *in ref*, which I guess is same as 
*const ref* but with *scope*.

Assume I'm trying to decide on the signature of a setter 
property, I could do any of:
 property auto field(const ref Field f) {...}
 property auto field(in ref Field f) {...}
 property auto field(in Field f) {...}

If Field is big *const ref* clearly wins. Even if Field is small 
now it does not mean it will not grow in the future and become an 
issue. But when does *in* win?

I wrote a small benchmark comparison and get the following 
results. If it is a bogus comparison for whatever reason let me 
know. It seems if performance is the only issue, just use *const 
ref* or *in ref*.

Thanks
Dan

---------
2 bytes: using cref_(int size) took 39[ms]
2 bytes: using inref(int size) took 40[ms]
2 bytes: using in___(int size) took 31[ms]

4 bytes: using cref_(int size) took 29[ms]
4 bytes: using inref(int size) took 29[ms]
4 bytes: using in___(int size) took 30[ms]

8 bytes: using cref_(int size) took 29[ms]
8 bytes: using inref(int size) took 28[ms]
8 bytes: using in___(int size) took 31[ms]

16 bytes: using cref_(int size) took 29[ms]
16 bytes: using inref(int size) took 29[ms]
16 bytes: using in___(int size) took 32[ms]

32 bytes: using cref_(int size) took 29[ms]
32 bytes: using inref(int size) took 29[ms]
32 bytes: using in___(int size) took 39[ms]

64 bytes: using cref_(int size) took 29[ms]
64 bytes: using inref(int size) took 29[ms]
64 bytes: using in___(int size) took 157[ms]

128 bytes: using cref_(int size) took 29[ms]
128 bytes: using inref(int size) took 29[ms]
128 bytes: using in___(int size) took 290[ms]

--------

import std.stdio;
import std.datetime;

struct S(int size) {
   char[size] c;
}

int x = 1;
void in___(int size)(in S!size t) { x++; }
void inref(int size)(in ref S!size t) { x++; }
void cref_(int size)(const ref S!size t) { x++; }
void compare(int size)() {
   callIt!(cref_,size);
   callIt!(inref,size);
   callIt!(in___,size);
   writeln();
}
 property void callIt(alias func, int size)() {
   const int iterations = 10_000_000;
   {
     S!size s;
     auto sw = StopWatch(AutoStart.yes);
     for(size_t i=0; i<iterations; ++i) {
       func(s);
     }
     sw.stop();
     writeln(size, " bytes: using ", func.stringof, " took ", 
sw.peek().msecs, "[ms]");
   }
}

void main() {
   compare!(2)();
   compare!(4)();
   compare!(8)();
   compare!(16)();
   compare!(32)();
   compare!(64)();
   compare!(128)();
}
Nov 06 2012
next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, November 06, 2012 22:52:53 Dan wrote:
 Later on another response is: "A huge difference between *in* and
 *const ref* which you don't cover at all is the fact that *const
 ref* must take an lvalue, whereas *in* doesn't have to"
 
 Why is this benefit huge? Is it just the convenience of being
 able to pass in literals or is it something more?

The difference between in and const ref, in is the same as const scope, and const ref is well, const ref. So, in doesn't do anything with ref and thus will copy most arguments to it (though copies do get elided in cases where the compiler determines that it can do a move instead), whereas const ref won't copy the variable, but it _will_ require an lvalue. If a function requires an lvalue, then you have to pass a variable to it. Not only does that mean that you can't pass literals to it, but you can't pass the result of another function to it. For instance, if writefln required lvalues, you couldn't do writefln("%s(%s): stray message %s: %s", __FILE__, __LINE__, foo(), bar(7)); You'd have to do something like string file = __FILE__; size_t line = __LINE__; auto f = foo(); auto b = bar(7); writefln("%s(%s): stray message %s: %s", f, b); That would be _really_ annoying. And the same goes for functions in general. Functions which require lvalues are generally not user friendly unless you're specifically looking to alter the variable passed in, in which case const ref doesn't do what you want anyway.
 Also, it does not mention *in ref*, which I guess is same as
 *const ref* but with *scope*.

Yes. in is an alias for const scope, ad I would advise avoiding scope as much as possible (so, I'd advise avoiding in as much as possible). The only time that it works correctly right now is with delegates. In all other cases, it's ignored, and once that's fixed, it'll break code all over the place. scope is supposed to prevent the variable that you pass in from being escaped from that function, which is sometimes useful (particularly with delegates) but is often overly restrictive. And even if that's what you want, if it's not a delegate, it doesn't get checked right now, so using scope or in gains you nothing anyway. It's just better to avoid it.
 I wrote a small benchmark comparison and get the following
 results. If it is a bogus comparison for whatever reason let me
 know. It seems if performance is the only issue, just use *const
 ref* or *in ref*.

ref avoids a copy, so it will be faster in cases where the copy would be expensive. That's why people use const& in C++ and why so many people are annoyed with const ref in D requiring an lvalue. auto ref was supposed to solve the problem, but it currently only works with templates. It's still up in the air how this problem is going to be solved. - Jonathan M Davis
Nov 06 2012
prev sibling parent "Dan" <dbdavidson yahoo.com> writes:
Very clear and much appreciated.

On Tuesday, 6 November 2012 at 23:38:00 UTC, Jonathan M Davis 
wrote:
 If a function requires an lvalue, then you have to pass a

 You'd have to do something like

 string file = __FILE__;
 size_t line = __LINE__;
 auto f = foo();
 auto b = bar(7);

 writefln("%s(%s): stray message %s: %s", f, b);

 That would be _really_ annoying. And the same goes for 
 functions in general.
 Functions which require lvalues are generally not user friendly 
 unless you're
 specifically looking to alter the variable passed in, in which 
 case const ref
 doesn't do what you want anyway.

Yeah - that would be quite annoying. I would say it like "functions which require lvalues are generally not *programmer* friendly, but as the size of the struct grows they do become *user* friendly."
 Also, it does not mention *in ref*, which I guess is same as
 *const ref* but with *scope*.

Yes. in is an alias for const scope, ad I would advise avoiding scope as much as possible (so, I'd advise avoiding in as much as possible).

Ok. Taking this into account I'll avoid *in*. My new heuristic for setters (and struct param passing in general) will be to choose between *const ref* and no storage specifier at all. For small structs (say < 32 bytes) I'll just have (T t) and for large structs I'll have (const ref T t). I don't see why the compiler can't determine that for me and keep it transparent. I guess if it did make such an arbitrary decision many people would be upset. Thanks Dan
Nov 07 2012