"ref const" parameters in functions

L-MAN vangelisforever at yandex.ru
Sun Apr 1 20:46:24 PDT 2012


On Sunday, 1 April 2012 at 08:22:08 UTC, Jonathan M Davis wrote:
> On Sunday, April 01, 2012 09:10:58 L-MAN wrote:
>> On Saturday, 31 March 2012 at 21:42:05 UTC, Jonathan M Davis
>> 
>> wrote:
>> > On Saturday, March 31, 2012 23:25:51 L-MAN wrote:
>> >> Hello everybody!
>> >> 
>> >> I'm trying to use some function FN like this:
>> >> 
>> >> struct X
>> >> {
>> >> 
>> >>   protected double _x;  // double type for example
>> >>   public @property double X() const { return _x; }
>> >> 
>> >>   // ctor
>> >>   public this(double x) { _x = x; } // double type for 
>> >> example
>> >> 
>> >>   void FN(ref const(double) in_x) // double type for example
>> >>   {
>> >> 
>> >>     // ... do some operations with _x
>> >> 
>> >>   }
>> >> 
>> >> }
>> >> 
>> >> main(..)
>> >> {
>> >> 
>> >>   ....
>> >>   X x = X(20);
>> >> 
>> >>   x.FN(30); // getting an error
>> >> 
>> >> }
>> >> 
>> >>   why x.FN(20) gets me an error?
>> >>   the construction "ref const(double)" or "ref
>> >> 
>> >> immutable(double)"
>> >> must be an rvalue by default I think, but in FN parameter 
>> >> the
>> >> compiler expects an lvalue...
>> >> 
>> >>   this strategy is the way to some unnecessary copy 
>> >> operations,
>> >> 
>> >> when value 20 (or some big struct instead of it) will copy 
>> >> to
>> >> the
>> >> stack..
>> >> 
>> >> How can I resolve this problem?
>> > 
>> > Unlike C++, const ref _must_ be an lvalue just like ref. If 
>> > you
>> > use auto ref
>> > instead of const ref, the compiler is supposed to choose
>> > between ref and non-
>> > ref based on which it thinks would be more efficient, but it
>> > currently only
>> > works with templated types.
>> > 
>> > You can duplicate the function and have a version whose
>> > parameter is const ref
>> > and one which is not, but be careful if you try and make the
>> > non-const ref
>> > version call the const ref version (to avoid duplicating the
>> > function's body)
>> > and make sure that you don't get infinite recursion. It 
>> > usually
>> > works, but I've
>> > run into cases before where I ended up with infinite 
>> > recursion,
>> > so make sure
>> > that you have unit tests which check.
>> > 
>> > Regardless, if you're dealing with a primitive type like
>> > double, don't bother
>> > with const ref. It's not going to be more efficient. It's 
>> > with
>> > large structs
>> > that you can see a difference.
>> > 
>> > - Jonathan M Davis
>> 
>> Thank you for reply, but how can I exclude some copy 
>> operations?
>> Look at this sample:
>> 
>> struct ABC // simple class
>> {
>> 	private double _d=0;
>> 	@property
>> 	{
>> 		double D() const { return _d; }
>> 		double D(double val) { return _d=val; }
>> 	}
>> 
>> 	this(double d)
>> 	{
>> 		_d = d;
>> 		writeln(" *** ctor ABC, d = ", _d);
>> 	}
>> 
>> 	ABC opAdd(ABC abc)
>> 	{
>> 		//ABC temp = ABC(this._d+abc._d);
>> 		//return temp;
>> 		return ABC(this._d+abc._d);
>> 	}
>> 
>> 	ABC opMul(double d)
>> 	{
>> 		//ABC temp = ABC(this._d*d);
>> 		//return temp;
>> 		return ABC(this._d*d);
>> 	}
>> 
>> 	ref ABC opAssign(ABC abc)
>> 	{
>> 		this._d = abc.D;
>> 		return this;
>> 	}
>> 
>> 	~this()
>> 	{
>> 		writeln(" *** dtor ABC, d = ", _d);
>> 	}
>> }
>> 
>> 
>> struct F {
>> 
>> 	private ABC _abc;
>> 
>> 	@property double D() const { return _abc.D; }
>> 
>> 	public this(ABC abc)
>> 	{
>> 		_abc = abc;
>> 
>> 		//abc.D=90;
>> 		//pnt.X = 30;
>> 	}
>> }
>> 
>> 
>> void main(string[] args)
>> {
>>    ABC abc1 = ABC(10);
>>    ABC abc2 = ABC(20);
>>    F f = F(abc1+abc2*20.0);
>>    writeln(f.D);
>>   .......
>> }
>> 
>> Operation abc1+abc2*20.0 consists of 2 ABC copy:
>> 1. Copy result of abc2*20.0 temp value to opAdd function
>> 2. Copy result of abc1+abc2*20.0 temp2 value to F() ctor
>> two temp variables and two copy operations.
>> But if I can use "ref const" rvalue operations 0 copies will be
>> need:
>>    abc2*20.0 creates temp valiable $$$temp
>>    opAdd(ref const ABC abc)  use a ref rvalue to $$$temp, is 
>> not a
>> copy
>>    opAdd return a $$$temp2 that use as ref rvalue for F() ctor
>> And try to imagine how many unnecessary copies of ABC will 
>> create
>> a simple operation like this:
>>   ((abc1+abc2*20.0)/0.25) + (abc1/abc2)*(abc2-1)....
>>    it killing a time of application, especially if time is
>> critical.
>> 
>> The constructions "auto ref" in return functions values are not
>> working well, if I use it in a "auto ref opAdd(ref const ABC)"
>> "auto ref opMul(double d)" " F.this(ref const ABC)" to exclude
>> some copy operations it gives me a strange result :-((
>
> You misunderstood what I meant by auto ref. I meant that you 
> use auto ref
> instead of const ref. e.g.
>
> void func(auto ref T param) {}
>
> At present, func would have to be templated to use auto ref
>
> void func(T)(auto ref T param) {}
>
> but eventually, auto ref shouldn't need the function to be 
> templated.
> Returning auto ref is completely different. It says that the 
> return type is
> inferred (just like with returning auto by itself) except that 
> it's a
> reference to the type which is inferred. And that requires that 
> the value
> being returned by an lvalue which is _not_ a local variable 
> (because in all
> cases except for auto ref on a parameter, ref must refer to an 
> lvalue, and if
> you return a ref to a local variable, you're going to get 
> undefined behavior,
> because you're returned a reference to a variable which no 
> longer exists). You
> can return ref from a function if you're returning the object's 
> this pointer,
> but that's generally all.
>
> If you're really paranoid about copying, then you're going to 
> need to either
> us  auto ref on your parameters (which currently means that the 
> functions must
> be templated), or you need to use const ref (which then 
> requires that you use
> lvalues with those functions). Something like
>
>  ((abc1+abc2*20.0)/0.25) + (abc1/abc2)*(abc2-1)....
>
> makes _no_ sense if you're trying to avoid copying. By 
> definition, those
> operations must create temporaries, because they're returning 
> new values.
> That's exactly what happens with the built-in types. +, *, /, 
> etc. all create
> temporaries. You need to use +=, *=, /=, etc if you don't want 
> to create
> temporaries. The same would be true in C++.
>
> But honestly, in your example, there is _no_ reason to worry 
> about it. You
> have a struct that holds one double, and that's it. So, it's 
> going to be about
> as efficient as if all of those operations were operating on a 
> double rather
> than a struct. If your struct had ten different variables, then 
> maybe you'd
> need to be worried about copying, but not with just one.
>
> Regardless, if you want to avoid creating temporaries, you 
> can't use operators
> which return new values. You need to use the opOpAssign 
> operators, which means
> that your code is going to look very different, because you 
> won't be able to
> create expressions like the one above.
>
> By using auto ref and/or const ref, you can create functions 
> which don't make
> copies when passed a variable, but as long as you're using 
> functions/operators
> which return new values, you're still going to be creating 
> temporaries.
>
> - Jonathan M Davis

About temporaries in operators +-/*.. you're right, it is not a 
secret.
references in other functions (not in operators) too.


I can't understand one thing:
  ABC abc; // ABC is a struct that consists of some 4x4 matrix for 
example
  ABC abc2 = ABC(20);
  ABC abc3 = ABC(..);
  .........
  abc+=abc2+abc3;

  operation "+" return the copy of it result to the internal 
temporary stack variable $temp (it creates automatically and it 
disable to use and invisible to see), but $temp is a variable and 
it must be an lvalue always. Expression "ref const.." in function 
parameter is an lvalue in D. Why $temp variable can't be passes 
as "ref const..." to the += operator or any other orepators)?
I think, that the $temp in D is a rvalue, but expression "ref 
const.." is an lvalue.. some paradox, and out of understanding..

if "ref const.." in operators parameters would be an rvalue, the 
execution of the next cycle be a 2x faster:


// "ref const.." is an lvalue. ***********************
ABC sum;
.....
         for (int i = 0; i<100000000; i++)
	{
		sum = (abc1+abc2*20.0)+(abc1*abc2+abc1*20.0);
                //abc2.opMul(20.0) creates $temp1;
                //abc1.opAdd(copy of $temp1) creates $temp2
                //abc1.opMul(copy of abc2) creates $temp3
                //abc1.opMul(20.0) creates $temp4
                //$temp3.opAdd(copy of $temp4) creates $temp5
                //$temp2.opAdd(copy of $temp5) creates $temp6
                //sum.opAssign(copy of $temp6)
	}

RESULT: execution time 17.8 seconds
5 unnecessary copy operations:
   1. copy of $temp1
   2. copy of $abc2
   3. copy of $temp4
   4. copy of $temp5
   5. copy of $temp6

// "ref const.." is an rvalue: **************************
ABC sum;
.....
         for (int i = 0; i<100000000; i++)
	{
		sum = (abc1+abc2*20.0)+(abc1*abc2+abc1*20.0);
                //abc2.opMul(20.0) creates $temp1;
                //abc1.opAdd(ref to $temp1) creates $temp2
                //abc1.opMul(ref to abc2) creates $temp3
                //abc1.opMul(20.0) creates $temp4
                //$temp3.opAdd(ref to $temp4) creates $temp5
                //$temp2.opAdd(ref to $temp5) creates $temp6
                //sum.opAssign(ref to $temp6)
	}

  RESULT: execution time 9.2 seconds
   no unnecessary copy operations.

when I draw to the screen 50000 graphical objects by OpenGL, it 
uses a matrices operation via D-language structures (uses all 
math operators and makes some matrices operations for every 
graphical object on the scene for each time), the unnecessary 
copyes of it(matrices) is very bad for engine performance.

Thank you!



More information about the Digitalmars-d-learn mailing list