Sign in to follow this  
Lode

copies of return value?

Recommended Posts

In the following 2 code examples, in the second example there is a more convenient notation in the main() function, but normally an extra copy of the texture is made in the changeTexture function. Do you know if there is really made a copy, or does the compiler optimize it? Are rvalue references of C++09 something that could allow a way to do that of the second example, but without copying?
//purely fictive example with "Texture" that contains all the pixels so that copying a texture is expensive

void changeTexture(Texture& out, const Texture& in)
{
  for(size_t i = 0; i < in.numpixels; i++)
  {
    out.setPixel(changePixel(in.getPixel(i)));
  }
}

int main()
{
  Texture t("fancytexture.png");

  Texture changed;
  changeTexture(changed, t);
}


////////////////////////////////////////////////////////////////////////////////

//in the version below, that allows a nicer notation in the main() function, is there an extra copy made resulting in slower processing?

Texture ChangeTexture(const Texture& in)
{
  Texture result;
  for(size_t i = 0; i < in.numpixels; i++)
  {
    result.setPixel(changePixel(in.getPixel(i)));
  }
  return result;
}

int main()
{
  Texture t("fancytexture.png");

  Texture changed = changeTexture(t); 
}


Share this post


Link to post
Share on other sites
Quote:
Do you know if there is really made a copy, or does the compiler optimize it?

Most modern compilers optimize it when you turn optimizations on (I think /O2 is the lowest optimization level in Visual Studio 2005 which performs this optimization). It is known as the NRVO (named return value optimization) and for more information I suggest you read at least one of the following:
Named Return Value Optimization in Visual C++ 2005
The Name Return Value Optimization by Stan Lippman
"Returning a Class Object" from Efficient C++ Programming (also by Stan Lippman and uses almost the same examples as the previous)

From the first article:
Quote:
Typically, when a method returns an instance of an object, a temporary object is created and copied to the target object via the copy constructor. The C++ standard allows the elision of the copy constructor (even if this results in different program behavior), which has a side effect of enabling the compiler to treat both objects as one (see section 12.8. Copying class objects, paragraph 15; see Reference). The Visual C++ 8.0 compiler makes use of the flexibility that the standard provides and adds a new feature: Named Return Value Optimization (NRVO). NRVO eliminates the copy constructor and destructor of a stack-based return value. This optimizes out the redundant copy constructor and destructor calls and thus improves overall performance. It is to be noted that this could lead to different behavior between optimized and non-optimized programs (see the Optimization Side Effects section).


The second article seems to indicate that Visual C++ 7.1 didn't apply this optimization by default (it might if you change the optimization settings though, but I'm not sure).

Share this post


Link to post
Share on other sites
I've reverse engineered a lot of code and I'm 100% sure the second version will be optimized into the first unless there is some very specific reason not to. All the calls I've seen always result in the return value being passed as a pointer as a compiler generated extra parameter, thereby avoiding the copy.

This may vary with call types (cdecl, thiscall, fastcall, stdcall and so on) though.

Share this post


Link to post
Share on other sites
The only large problem with relying on such optimizations is that the compiler doesn't warn you when it stops applying them.

If your texture has a swap member, you can use something like:

template<typname T>
struct ReturnViaMove {
mutable T t;
ReturnViaMove( T const& other_ ):t() {
T& other = const_cast<T&>(other_);
other.swap(this->t);
}
ReturnViaMove( ReturnViaMove const& other_ ):t() {
this->t.swap(other_.t);
}
};

...

class Texture {
...
Texture(ReturnViaMove<Texture> const& rv) {this->swap(rv.t);}
Texture& operator=(ReturnViaMove<Texture> const& rv) {
this->swap(rv.t);
return *this;
}
};


which generates explicit move semantics for return values.

Ie:

ReturnViaMove<Texture> ChangeTexture(const Texture& in)
{
Texture result;
for(size_t i = 0; i < in.numpixels; i++)
{
result.setPixel(changePixel(in.getPixel(i)));
}
return result;
}

now becomes guaranteed to not generate a duplicate Texture, regardless of the optimization settings or any quirks in the implementation details of your functions (and assuming your default constructed Texture's are cheap).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this