Jump to content
  • Advertisement
Sign in to follow this  
Ro_Akira

Return Value Optimisation

This topic is 4920 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Q1. Is it me, or does the VC++ 6.0 compiler only use RVO when code is in this form:
const Foo Foo::SumRVO (int a, int b) const
{
    return Foo (a + b);
}

as opposed to this:
const Foo Foo::Sum (int a, int b) const
{
    Foo f (a + b);
    return f;
}

Q2. Would I be correct in stating that RVO is one optimisation, at least, that gets used whether it's debug mode or not? (I’m placing my money on yes). I have determined that RVO is being used by stepping through the code in debug mode and finding the Foo::Sum function calls the copy constructor. Q3. Is this a reasonable method of testing? May I point you to Document A: efficiency of operator+(), and also Document B: Performance Programming Applied to C++. Q4. Am I correct in saying that Foo::Sum holds an example of a "named object" f, and Foo::Sum contains an "unnamed object" as mentioned in Document A? Q5. Given the answer to Q4 is yes, is Document A concluding with the assertion that both Foo::Sum, and Foo::SumRVO may in fact be optimised by the compiler? Q6. Given the answer to Q5 is yes, are more recent compilers actually using RVO with Foo::Sum, and indeed more complex derivatives?

Share this post


Link to post
Share on other sites
Advertisement
A1 - Yes, the second form is NRVO (Named RVO), for which there was less support at the time

A2 - Correct to the extent of my personal experiences. RVO is an 'optimization' that actually changes the program's semantics. It would cause problems if they changed depending on your optimization setting.

A3 - Check whether the function creates a temporary variable for the return value, or directly constructs the function's target (i.e. whatever you assign the function's result to) with the parameters you passed to Foo's constructor.

A4 - Yes. See A1

A5 - Yes. See A1

A6 - Yes. See A1. Though there surely are limits to the complexity of expressions that can be so optimized.

Share this post


Link to post
Share on other sites
AFAIK, MSVC++Express Beta does perform RVO on your second example. There are a few conditions that must hold, of course. The object can't be used between its creation and its return, and its creation can't have side-effects that change the meaning of the code between the creation and the return. The compiler has to know for certain it will have no effect on the meaning of the program before it will optimize it, and it probably doesn't try all that hard before it decides not to do RVO.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ro_Akira
Q1. Is it me, or does the VC++ 6.0 compiler only use RVO when code is in this form:


I'm not sure if VC++ 6.0 optimizes both of them or not, but it is very likely that it only optimizes the first one. The second one is actually an example of NRVO (the named return value optimization), which only more modern compilers have been implementing. Both are completely standards compliant optimizations and both can potentially generate the same exact code.

Quote:
Original post by Ro_Akira
Q2. Would I be correct in stating that RVO is one optimisation, at least, that gets used whether it's debug mode or not? (I’m placing my money on yes).

If I remember correctly from the last time I tested an MS compiler, it does perform the return value optimization in debug mode. I'd personally believe that most compilers, if they perform the optimization, will do so in both builds since consistency between debug and release mode is very important when trying to solve a problem. Otherwise, you may get different results (bugs) between debug mode and release mode, particularly if the copy-constructor is not implemented properly.

Quote:
Original post by Ro_Akira
I have determined that RVO is being used by stepping through the code in debug mode and finding the Foo::Sum function calls the copy constructor.
Q3. Is this a reasonable method of testing?

Yes, but as you have mentioned, you can't really be certain that it's calling the copy-constructor in release mode solely based on what happens in debug. Again, though, I'd say it's a pretty good rationalization that if it doesn't perform NRVO in debug, then it doesn't do so in release either. Instead, just check in release by outputing "copy constructing" inside the copy constructor or something along those lines.

Quote:
Original post by Ro_Akira
Q4. Am I correct in saying that Foo::Sum holds an example of a "named object" f, and Foo::Sum contains an "unnamed object" as mentioned in Document A?

Foo::Sum has a named object, Foo::SumRVO has an unnamed object (I assume you typod and accidently wrote Foo:Sum twice).

Quote:
Original post by Ro_Akira
Q5. Given the answer to Q4 is yes, is Document A concluding with the assertion that both Foo::Sum, and Foo::SumRVO may in fact be optimised by the compiler?

Yes, both can be optimized to be exactly the same. The writer of document B apparently was unfamiliar with NRVO at the time of its writing, since he claims that the second form of his operator + can have one less copy construction, which is untrue, although odds are in older compilers it will perform the RVO but not the NRVO.

Quote:
Original post by Ro_Akira
Q6. Given the answer to Q5 is yes, are more recent compilers actually using RVO with Foo::Sum, and indeed more complex derivatives?

Yes, many modern compilers do take advantage of NRVO. One other optimization you should be aware of is that:

Foo new_object = some_foo( 1, 2 );

will be able to optimize out the copy construction from the return value to the newly created object (note: the object is being constructed, not assigned to). In turn, constructing an object based off of the return value of a function which can take advantage of the return value optimization can potentially result in code which produces absolutely no copy-constructions and assignments for the object. It can be constructed in-place.

E:f;b

Edit:

Quote:
Original post by Deyja
There are a few conditions that must hold, of course. The object can't be used between its creation and its return, and its creation can't have side-effects that change the meaning of the code between the creation and the return. The compiler has to know for certain it will have no effect on the meaning of the program before it will optimize it, and it probably doesn't try all that hard before it decides not to do RVO.

Actually, the object can be used and modified inside of the function. That should not have any effect on whether or not the NRVO can be performed (which is one of the reasons that it is so powerful).

Share this post


Link to post
Share on other sites
Thanks you all for your replies.

Original post by Fruny
It would cause problems if they changed depending on your optimisation setting.

Original post by Polymorphic OOP
Otherwise, you may get different results (bugs) between debug mode and release mode, particularly if the copy-constructor is not implemented properly.

Agreed.

Interestingly, if the copy constructor does something such as output something to the screen, it will get swallowed up by RVO. I can't forsee a case where that would be a problem in practice though. One's copy constructor generally sticks to the task of 'copy constructing' and no more.

Quote:
Original post by Polymorphic OOP
Foo::Sum has a named object, Foo::SumRVO has an unnamed object (I assume you typod and accidentally wrote Foo:Sum twice).

You assumed correctly sir!

I believe I have a good understanding of RVO now.

Share this post


Link to post
Share on other sites
I spoke too soon. I still have this to understand:

class Foo
{
public:

Foo (int a, int b) : value (a + b)
{
}

Foo (const Foo& foo) : value (foo.value)
{
}

const Foo operator+ (const Foo& foo)
{
return Foo (value, foo.value);
}
};

int main ()
{
Foo f0, f1;

// The result of f0 + f1 is a const Foo?
Foo f2 (f0 + f1);

// Acts like this, because of RVO. The const nature of the
// return value of operator+ is ignored.
// Foo f2 (f0.value, f1.value);

return 0;
}


Is this making a small mockery of const? If it was executed the non-RVO way, I can understand, because it's making a copy in the copy constructor, from the const Foo returned by operator+. I guess it does not affect most programs in practice.

Share this post


Link to post
Share on other sites
Quote:
Original post by Ro_Akira

int main ()
{
Foo f0, f1;

// The result of f0 + f1 is a const Foo?
Foo f2 (f0 + f1);

// Acts like this, because of RVO. The const nature of the
// return value of operator+ is ignored.
// Foo f2 (f0.value, f1.value);

return 0;
}

Is this making a small mockery of const? If it was executed the non-RVO way, I can understand, because it's making a copy in the copy constructor, from the const Foo returned by operator+. I guess it does not affect most programs in practice.

It's not really making a mockery of const, it's kind of like having a non-const object that is being referenced by both a reference to the type and a reference to the const-qualified type. In that case, both references can refer to the same object without logical error and in each of their contexts, the object can be worked with under different limitations.

The same type of logic is true here. You know that when the copy constructor is implemented properly, whether a copy takes place or the object is constructed in place is an implementation detail. As well, you know that you can reference a non-const object validly in both a const and non-const manner, so while the return type may be const and the returned value is not (or the similar situation with an object being initialized), the same object can be used without any logical problems. It just means that when using the object as the return value it may be refered to with const-qualification, whereas inside the function it may not be.

Share this post


Link to post
Share on other sites
Quote:
Original post by Polymorphic OOP
...you know that you can reference a non-const object validly in both a const and non-const manner, so while the return type may be const and the returned value is not (or the similar situation with an object being initialized), the same object can be used without any logical problems. It just means that when using the object as the return value it may be refered to with const-qualification, whereas inside the function it may not be.


I understand completely! Cheers!

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!