pre return optimization

  • Thread starter terminator(jam)
  • Start date
T

terminator(jam)

consider:

struct memory_pig{//a really large type:

memory_pig(){
std::cout<<"mem pig default\n";
//etc...
};

memory_pig(memory_pig const&){
std::cout<<"mem pig copy\n";
//etc...
};

~memory_pig(){
std::cout<<"mem pig finish\n";
//etc...
};

//etc...

};///struct memory_pig

memory_pig foo(){
memory_pig result;
result=something;
//etc...
result=something_else;
return result;
};

any time 'foo' is called the output will contain the following
sequence:


mem pig default
mem pig copy
mem pig finish

the last line of output may repeat based on how the result is
stored(rvo) or not.
So,two objects of a large type will be constructed and at least one is
destructed on every call to 'foo' in PASCAL you can write:

function foo:memory_pig
begin
foo:=something;
{etc...}
foo:=somthing_else;
end

that is you can refrence the returned object inside the function and
decrease the overhead for copying large objects.C++ lacks such syntax
and IMHO we should be able to mark the result object as referencing
the actual return so that there is no need for the extra copy
construction;this is espesifically beneficall when dealing with
operator definitions .We can declare the return itself as an object.I
suggest the following syntax:

class ret_type funxn (paramlist){
ret_type return /*optional:*/(initiallizer params);

//etc...

if(false)return;//return like void functions

//return something;//error:named return accepts no param.

return=something;//ok;
return=something_else;
return.member_funxn();
another_funcxn(return);

};

Provided that 'return' is declared as an object { whenever you use the
'return' keyword inside pharantesis or accompanied via an operator ?
the return object is referenced :eek:therwise a parameterless 'return'
returns to the caller.
The return object is an lvalue inside the function and can be aliased
for readability:

memory_pig foo(){
memory_pig return , & result=return;
result=something;
//etc...
result=something_else;
};

and whenever 'foo' is called the output looks like this:

mem pig default

the unnessesary copy/move has vanished and the destruction of
temporary depends on what you do with it.

regards,
FM.
 
G

Guest

consider:

struct memory_pig{//a really large type:

memory_pig(){
std::cout<<"mem pig default\n";
//etc...
};

memory_pig(memory_pig const&){
std::cout<<"mem pig copy\n";
//etc...
};

~memory_pig(){
std::cout<<"mem pig finish\n";
//etc...
};

//etc...

};///struct memory_pig

memory_pig foo(){
memory_pig result;
result=something;

This should be

memory_pig result = something;

or

memory_pig result(something);

//etc...
result=something_else;
return result;
};

any time 'foo' is called the output will contain the following
sequence:


mem pig default
mem pig copy
mem pig finish

the last line of output may repeat based on how the result is
stored(rvo) or not.


With some smart coding you can transform the above to:

memory_pig foo(){
// ...
return memory_pig(something, something_else);
}

And with RVO you should only have one constructor call.
So,two objects of a large type will be constructed and at least one is
destructed on every call to 'foo' in PASCAL you can write:

function foo:memory_pig
begin
foo:=something;
{etc...}
foo:=somthing_else;
end

that is you can refrence the returned object inside the function and
decrease the overhead for copying large objects.

I am not familiar with what that exactly means in Pascal, is it
something similar to

void foo(memory_pig& ret) {
// ...
ret = something;
// ...
ret = something_else;
}

Another thing that you seem to forget is that for large objects the
assignment is not cheap either, sometimes even more costly than a
constructor call.
 
J

James Kanze

terminator(jam) said:
consider:
struct memory_pig{//a really large type:
memory_pig(){
std::cout<<"mem pig default\n";
//etc...
};
memory_pig(memory_pig const&){
std::cout<<"mem pig copy\n";
//etc...
};
~memory_pig(){
std::cout<<"mem pig finish\n";
//etc...
};

};///struct memory_pig
memory_pig foo(){
memory_pig result;
result=something;
//etc...
result=something_else;
return result;
};
any time 'foo' is called the output will contain the following
sequence:
mem pig default
mem pig copy
mem pig finish

I'm not sure I understand. Even in a worst case scenario, I
can't see where there would be more than two copies of
memory_pig in memory at a given time. Given the explicit
authorization of NRVO by the standard, I would expect that
in most implementations, there is never more than one.
the last line of output may repeat based on how the result
is stored(rvo) or not. So,two objects of a large type
will be constructed and at least one is destructed on
every call to 'foo' in PASCAL you can write:
function foo:memory_pig
begin
foo:=something;
{etc...}
foo:=somthing_else;
end

Not can, in Pascal, you have to write it that way.

It basically comes out to the same thing: the Pascal
compiler generates a local variable with the name of foo for
the return; if the name foo is used on the left hand side of
an assignment, it refers to the local variable, and if it is
used on the right hand side, it refers to the function.

And of course, in C++, unlike in Pascal, you can return an
expression directly, without first assigning it to a local
variable.
that is you can refrence the returned object inside the
function and decrease the overhead for copying large
objects. C++ lacks such syntax and IMHO we should be able
to mark the result object as referencing the actual return
so that there is no need for the extra copy construction;
this is espesifically beneficall when dealing with
operator definitions.

The current C++ standard leaves this up to the
implementation, but explicitly allows it. The situation in
C++ is more complicated than in Pascal, however, since you
can return from anywhere, e.g.:

memory_pig
foo()
{
memory_pig result ;
// ...
if ( someCondition ) {
memory_pig aDifferentResult ;
// ...
return aDifferentResult ;
}
// ...
return result ;
}

Whether this is a feature or a defect could be debated, but
it certainly cannot be removed from the language without
breaking a considerable amount of existing code.
 
T

terminator

I'm not sure I understand. Even in a worst case scenario, I
can't see where there would be more than two copies of
memory_pig in memory at a given time. Given the explicit
authorization of NRVO by the standard, I would expect that
in most implementations, there is never more than one.


Not can, in Pascal, you have to write it that way.

It basically comes out to the same thing: the Pascal
compiler generates a local variable with the name of foo for
the return; if the name foo is used on the left hand side of
an assignment, it refers to the local variable, and if it is
used on the right hand side, it refers to the function.

C++ generates an unnamed local variable which is not destructed by the
function ,this variable is constructed right before destruction of
other automatic variable which means it does not overwrite any
existing automatic variable ,So it has to be allocated in a separate
portion of [stack]memory.I just think it is posible to guess where the
'return' value is to be placed right at the beginning of the
function,so it can be constructed and refrenced before returning.
the choice between two modes of returning(name vs traditional) is left
to the programmer.
And of course, in C++, unlike in Pascal, you can return an
expression directly, without first assigning it to a local
variable.


The current C++ standard leaves this up to the
implementation, but explicitly allows it. The situation in
C++ is more complicated than in Pascal, however, since you
can return from anywhere, e.g.:

memory_pig
foo()
{
memory_pig result ;
// ...
if ( someCondition ) {
memory_pig aDifferentResult ;
// ...
return aDifferentResult ;
}
// ...
return result ;
}

Whether this is a feature or a defect could be debated, but
it certainly cannot be removed from the language without
breaking a considerable amount of existing code.

I believe it is a feature for many cases and a defect in many other
cases (operator functions)and I do not mean to remove anything from
the existing language.I just want to make it possible avoid the second
construction that happens at the end of a function (either move or
copy).

regards,
FM.
 
T

terminator

This should be

memory_pig result = something;

or

memory_pig result(something);





With some smart coding you can transform the above to:

memory_pig foo(){
// ...
return memory_pig(something, something_else);

}

And with RVO you should only have one constructor call.




I am not familiar with what that exactly means in Pascal, is it
something similar to

void foo(memory_pig& ret) {
// ...
ret = something;
// ...
ret = something_else;

}

yes,but just fancy yourself definning an operator instead of a normal
function.If operators could be declared as objects rather than
functions ,one could declare them as instances of functor classes and
solve the problem.BTW it looks very hard to parse since this would
need major changes in semantics;I think working on the return will be
easier.
Another thing that you seem to forget is that for large objects the
assignment is not cheap either, sometimes even more costly than a
constructor call.

that was just an illustration replace it with compound
assignment(+= , ...) or any other sort of referencing( member/static
function,...) . I just want to return the object which is processed in
the function,not a copy/moved object .

regards,
FM.
 
G

Guest

yes,but just fancy yourself definning an operator instead of a normal
function.If operators could be declared as objects rather than
functions ,one could declare them as instances of functor classes and
solve the problem.

In C++ operators and functions are the same thing, operators just have a
fancier syntax. I still do not see the problem, please explain in more
detail.
BTW it looks very hard to parse since this would
need major changes in semantics;I think working on the return will be
easier.

I do not know how hard the above is to parse but all C++ compilers that
I know of can do it, it is very standard C++.
 
T

terminator

In C++ operators and functions are the same thing, operators just have a
fancier syntax. I still do not see the problem, please explain in more
detail.

**I certainly do disaggree.** operators follow a dedicated syntax ,you
cannot change a binary operator to a void return ternary one:

void operator+(const A&,const A&,A&);//syntax error.

while you can choose either of the following:

void foo(A&);//your suggestion
A foo();//original context

So since the operators generally do return a value ,I prefer to work
on the return value itself ,not on an object which is going to be
copied in the return location:

class A A::eek:perator-(){
A return(initializer_params);
A& result=return;
modify(result);
if(some_circumstances())
return;//end of op-() return to caller
change(result);
DoWhatever(result);
};//end of op-() return to caller
I do not know how hard the above is to parse but all C++ compilers that
I know of can do it, it is very standard C++.
If operators could be declared as objects rather than
functions ,one could declare them as instances of functor classes and
solve the problem.BTW it looks very hard to parse since this would
need major changes in semantics;I think working on the return will be
easier.

that meant to treat operators as objects(not functions),which was a
stupid idea.

regards,
FM.
 
G

Guest

**I certainly do disaggree.** operators follow a dedicated syntax ,you
cannot change a binary operator to a void return ternary one:

What I meant was that overloaded operators are just special functions.
While they do have special syntax, special precedence rules, and only a
few allowed forms, when it comes to the generated code it is just like
any other function call. Thus they are only functions with fancier syntax.
that meant to treat operators as objects(not functions),which was a
stupid idea.

If you look at your post that I replied to it looked like you were
referring to the piece of code that I wrote, which is why I replied as I
did.
 
G

Greg Herlihy

consider:

struct memory_pig{//a really large type:

memory_pig(){
std::cout<<"mem pig default\n";
//etc...
};

memory_pig(memory_pig const&){
std::cout<<"mem pig copy\n";
//etc...
};

~memory_pig(){
std::cout<<"mem pig finish\n";
//etc...
};

//etc...

};///struct memory_pig

memory_pig foo(){
memory_pig result;
result=something;
//etc...
result=something_else;
return result;

};

any time 'foo' is called the output will contain the following
sequence:

mem pig default
mem pig copy
mem pig finish

the last line of output may repeat based on how the result is
stored(rvo) or not.
So,two objects of a large type will be constructed and at least one is
destructed on every call to 'foo'

The C++ Standard already allows the "(Named) Return Value
Optimization" (NRVO or RVO for short). The optimization allows (under
certain conditions) the compiler to construct the result of a function
call "in place" - that is, directly in the object initialized with the
function call result.

So, to take advantage of this optimization, a program should use the
result of a function call to initialize an object, instead of
assigning the function result to an existing object.

For example:

#include <iostream>

using std::cout;

struct memory_pig
{ // a really large type:
memory_pig()
{
cout << "mem pig default\n";
}
memory_pig(memory_pig const&)
{
cout << "mem pig copy\n";
}
~memory_pig()
{
cout << "mem pig finish\n";
}
};

memory_pig foo()
{
memory_pig result;
// ...
return result;
}

int main()
{
memory_pig m1 = foo();
memory_pig m2 = foo();
memory_pig m3 = foo();
}

I compiled the above program twice, once with and once without NRVO.
The output of both programs is shown in the two columns below. As this
comparison shows, NRVO can be a particular effective optimization -
even for a small C++ program like the one used in this example.

Program Output

Without NRVO: With NRVO:

mem pig default mem pig default
mem pig copy mem pig default
mem pig finish mem pig default
mem pig copy mem pig finish
mem pig finish mem pig finish
mem pig default mem pig finish
mem pig copy
mem pig finish
mem pig copy
mem pig finish
mem pig default
mem pig copy
mem pig finish
mem pig copy
mem pig finish
mem pig finish
mem pig finish
mem pig finish

Greg
 
G

Guest

I knew about RVO but not NRVO(that is what I am talking about).Is NRVO
standard behavoir or unspecified?
At least my compiler does not perform the NRVO.

The standard allows this optimisation, but it does not require it (see
section 12.8 paragraph 15). I think that most newer compilers support
it, but you might have to turn up the optimisation level a bit.

One important thing to remember is that when NRVO is in use the
destructor will not be called for the local object, nor will the copy-
constructor for the non-local object (m1 to m3 in the code above) be
called. Thus you should not depend on any side effects that the copy-
constructor or destructor might have.
 
T

terminator

The C++ Standard already allows the "(Named) Return Value
Optimization" (NRVO or RVO for short). The optimization allows (under
certain conditions) the compiler to construct the result of a function
call "in place" - that is, directly in the object initialized with the
function call result.

So, to take advantage of this optimization, a program should use the
result of a function call to initialize an object, instead of
assigning the function result to an existing object.

For example:

#include <iostream>

using std::cout;

struct memory_pig
{ // a really large type:
memory_pig()
{
cout << "mem pig default\n";
}
memory_pig(memory_pig const&)
{
cout << "mem pig copy\n";
}
~memory_pig()
{
cout << "mem pig finish\n";
}
};

memory_pig foo()
{
memory_pig result;
// ...
return result;
}

int main()
{
memory_pig m1 = foo();
memory_pig m2 = foo();
memory_pig m3 = foo();
}

I compiled the above program twice, once with and once without NRVO.
The output of both programs is shown in the two columns below. As this
comparison shows, NRVO can be a particular effective optimization -
even for a small C++ program like the one used in this example.

Program Output

Without NRVO: With NRVO:

mem pig default mem pig default
mem pig copy mem pig default
mem pig finish mem pig default
mem pig copy mem pig finish
mem pig finish mem pig finish
mem pig default mem pig finish
mem pig copy
mem pig finish
mem pig copy
mem pig finish
mem pig default
mem pig copy
mem pig finish
mem pig copy
mem pig finish
mem pig finish
mem pig finish
mem pig finish
I knew about RVO but not NRVO(that is what I am talking about).Is NRVO
standard behavoir or unspecified?
At least my compiler does not perform the NRVO.

thanx,
FM.
 
T

terminator(jam)

The standard allows this optimisation, but it does not require it (see
section 12.8 paragraph 15). I think that most newer compilers support
it, but you might have to turn up the optimisation level a bit.

One important thing to remember is that when NRVO is in use the
destructor will not be called for the local object, nor will the copy-
constructor for the non-local object (m1 to m3 in the code above) be
called. Thus you should not depend on any side effects that the copy-
constructor or destructor might have.

prior to return and after return are two different case to me:I mean
before return even move should be disabled ,while after the return
either of move/copy must perform .

regards,
FM.
 
G

Guest

===================================== MODERATOR'S COMMENT:

Please trim unnecessary material when replying.


===================================== END OF MODERATOR'S COMMENT
prior to return and after return are two different case to me:I mean
before return even move should be disabled ,while after the return
either of move/copy must perform .

Sorry, but you lost me there, what move? Of what to where?
 
I

int19h

consider:

struct memory_pig{//a really large type:

memory_pig(){
std::cout<<"mem pig default\n";
//etc...
};

memory_pig(memory_pig const&){
std::cout<<"mem pig copy\n";
//etc...
};

~memory_pig(){
std::cout<<"mem pig finish\n";
//etc...
};

//etc...

};///struct memory_pig

memory_pig foo(){
memory_pig result;
result=something;
//etc...
result=something_else;
return result;

};

any time 'foo' is called the output will contain the following
sequence:

mem pig default
mem pig copy
mem pig finish

It won't. It certainly won't do that with the example given, since
there's no clear association in it between the copy constructor and
the assignment operator (considering that the latter isn't even
defined). Assuming there was such an association, though, I'd rather
expect to see:

mem pig default
mem pig copy // first assignment
mem pig copy // second assignment
mem pig finish

Which is correct and fine - if you have explicitly written the
assignment operator twice, then surely you want the associated side-
effects? And if you do not, then perhaps you shouldn't have written it
that way.
that is you can refrence the returned object inside the function and
decrease the overhead for copying large objects.C++ lacks such syntax
and IMHO we should be able to mark the result object as referencing
the actual return so that there is no need for the extra copy
construction

This is precisely what NRVO does.

I have a feeling that you're confusing assignment with initialization
in this case. Assignment operator always applies to an already-
initialized object - the compiler cannot elide the initialization
except for the trivial cases (e.g. POD types), which is why operator=
still results in unnecessary copying even when the compiler implements
NRVO. It's not a problem with NRVO though - it's because assignment is
used where just initialization would suffice. Direct initialization is
still more efficient then assignment-following-initialization, even
with the move semantics of C++0x.
 
T

terminator(jam)

===================================== MODERATOR'S COMMENT:

Please trim unnecessary material when replying.

===================================== END OF MODERATOR'S COMMENT



Sorry, but you lost me there, what move? Of what to where?

Move ctor of course.

regards,
FM.
 
G

Guest

Move ctor of course.

Sorry, but I still do not understand. What do you mean with RNVO should
be disabled before return but not after? It is something that is
performed *during* return.
 
T

terminator(jam)

It won't. It certainly won't do that with the example given, since
there's no clear association in it between the copy constructor and
the assignment operator (considering that the latter isn't even
defined). Assuming there was such an association, though, I'd rather
expect to see:

mem pig default
mem pig copy // first assignment
mem pig copy // second assignment
mem pig finish
As I wrote before its just an illustration and I did not mean any
assignment,replace it with any function( eg. modify(result) ;).
Which is correct and fine - if you have explicitly written the
assignment operator twice, then surely you want the associated side-
effects? And if you do not, then perhaps you shouldn't have written it
that way.


This is precisely what NRVO does.

I was certain about the NRVO by the time I started the post.However
NRVO is a compiler option(ugly!!!) not a demand .With NRVO we have two
distinct phases optimize simultaniously : prior to return inside the
callee and after that inside the caller:

A foo(){
A ret;//this will be return under any conditions.
//....
return ret;//pre return :no copy/move
};
class A a=foo();//after return : copy/move to a

I have a feeling that you're confusing assignment with initialization
in this case. Assignment operator always applies to an already-
initialized object - the compiler cannot elide the initialization
except for the trivial cases (e.g. POD types), which is why operator=
still results in unnecessary copying even when the compiler implements
NRVO. It's not a problem with NRVO though - it's because assignment is
used where just initialization would suffice. Direct initialization is
still more efficient then assignment-following-initialization, even
with the move semantics of C++0x.

Everybody makes mistakes while learning , but confusing assignment
with copy certainly has never been the mistake I can make.

regards,
FM
 
T

terminator(jam)

Sorry, but I still do not understand. What do you mean with RNVO should
be disabled before return but not after? It is something that is
performed *during* return.

I mean the whole concept of optimization(rvo,nrvo,etc). IMHO:
If a function always returns the same local object the returned object
must be the local object itself rather than a copy/move version of the
local.
If a function returns one of its parameter values returned object is
copy/moved from that parameter because a change in location of the
object is almost inevitable.
If an object is constructed with an rvalue it should be copy/moved.

regards,
FM
 
G

Guest

I mean the whole concept of optimization(rvo,nrvo,etc). IMHO:
If a function always returns the same local object the returned object
must be the local object itself

You mean for a function such as

foo bar()
{
foo f;
return f;
}

NRVO should be used? That is (to my knowledge) the case if NRVO is
enabled in the compiler.
rather than a copy/move version of the local.

What is the difference between copy and move? I would say that NRVO is
move, since the object created in the function is moved to the
"surrounding" context/scope instead of being used as the basis of a copy.
If a function returns one of its parameter values returned object is
copy/moved from that parameter because a change in location of the
object is almost inevitable.

Assuming that you by copy/move mean that the object in the function is
being copied to the surrounding scope, did you mean that something like

foo bar(bool b)
{
foo f1, f2;
if (b)
return f1;
else
return f2;
}

should not use NRVO? Again, to my knowledge that is the case, since the
compiler can not predict which of the objects will be returned so a copy
is necessary.
If an object is constructed with an rvalue it should be copy/moved.

You mean something similar to

foo bar()
{
foo f1, f2;
return f1 + f2;
}

In that case NRVO could be used depending on whether the compiler knows
which object will be returned or not. So the above could utilise NRVO
but something like

foo bar(bool b)
{
foo f1, f2, f3;
if (b)
return f1 + f2;
else
return f2 + f3;
}

could not.

If the above is not what you meant, then I am sorry to have failed to
understand you. Perhaps you could try to rephrase the question or use
simple code examples.
 
I

int19h

A foo(){
A ret;//this will be return under any conditions.
//....
return ret;//pre return :no copy/move};

class A a=foo();//after return : copy/move to a

This is not the case. If the compiler correctly implements NRVO, the
example above will result in a single construction of A using the
default constructor, and then a subsequent destruction. No copies or
moves are made at any point.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,043
Latest member
CannalabsCBDReview

Latest Threads

Top