Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Return value optimization

Reply
Thread Tools

Return value optimization

 
 
Christian Meier
Guest
Posts: n/a
 
      04-03-2008
Hi NG

A few days ago there was a thread in comp.lang.c++.moderated with the
subject "Return versus Side-Effect". The OP asked whether to use "return by
value" or an additional parameter of type "C&" which will be overwritten
with a result.
The most posters advised the OP to use the version with the return value. No
only for aesthetic reasons but also for performance reasons because of the
return value optimization.
I wrote a little sample program to test their assertion.
For aesthetic reasons I prefer the "return by value" version. But I found
out that for performance reasons both versions are necessary. It depends on
how you call the function. Sometimes the "return by value" is faster than
the "out-parameter" version and sometimes the other way around. Can you
confirm my conclusion?

Best regards,
Chris





Here is my test code:


#include <iostream>
using std::cout;

#include <iostream>
using std::cout;

class MyString
{
public:
MyString() : value(NULL) { cout << "ctor 0\n"; }
MyString(const char*);
MyString(const MyString&);
~MyString() { delete[] value; }

MyString& operator=(const char* newValue);
MyString& operator=(const MyString&);

private:
char* value;
};


MyString::MyString(const char* newValue)
{
cout << "ctor 1\n";
if (newValue == NULL) {
value = NULL;
} else {
int length = strlen(newValue);
value = new char[length + 1];
strcpy(value, newValue);
}
}


MyString::MyString(const MyString& other)
{
cout << "copy ctor\n";
if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}
}


MyString& MyString:perator=(const char* newValue)
{
cout << "op =0\n";
if (value != NULL) {
delete[] value;
} // if

if (newValue == NULL) {
value = NULL;
} else {
int length = strlen(newValue);
value = new char[length + 1];
strcpy(value, newValue);
}
return *this;
}


MyString& MyString:perator=(const MyString& other)
{
cout << "op =1\n";
if (this == &other) {
return *this;
} // if

if (value != NULL) {
delete[] value;
} // if

if (other.value == NULL) {
value = NULL;
} else {
int length = strlen(other.value);
value = new char[length + 1];
strcpy(value, other.value);
}

return *this;
}


MyString returnByVal()
{
MyString ret("return value");
return ret;
}


void returnByParam(MyString& out)
{
out = "return value";
}



int main()
{
// First test case where the out parameter is faster.
cout << "Before returnByVal()\n";
MyString str1;
str1 = returnByVal();
cout << "\nBefore returnByParam()\n";
MyString str2;
returnByParam(str2);
cout << "Finished test 1\n\n\n";

// Second test case where return by value is faster.
cout << "Before returnByVal()\n";
MyString str3 = returnByVal();
cout << "\nBefore returnByParam()\n";
MyString str4;
returnByParam(str4);
cout << "Finished test 2\n";
}





The output is the following:

Before returnByVal()
ctor 0
ctor 1
op =1

Before returnByParam()
ctor 0
op =0
Finished test 1


Before returnByVal()
ctor 1

Before returnByParam()
ctor 0
op =0
Finished test 2


In the first test case the "out-parameter" version is faster.
In the second test case the "return by value" version is faster.


 
Reply With Quote
 
 
 
 
gpderetta
Guest
Posts: n/a
 
      04-03-2008
On Apr 3, 10:15 am, "Christian Meier" <chris@no_spam.com> wrote:
> Hi NG
>
> A few days ago there was a thread in comp.lang.c++.moderated with the
> subject "Return versus Side-Effect". The OP asked whether to use "return by
> value" or an additional parameter of type "C&" which will be overwritten
> with a result.
> The most posters advised the OP to use the version with the return value. No
> only for aesthetic reasons but also for performance reasons because of the
> return value optimization.
> I wrote a little sample program to test their assertion.
> For aesthetic reasons I prefer the "return by value" version. But I found
> out that for performance reasons both versions are necessary. It depends on
> how you call the function. Sometimes the "return by value" is faster than
> the "out-parameter" version and sometimes the other way around. Can you
> confirm my conclusion?
> [...]


Change this:

>
> MyString& MyString:perator=(const MyString& other)
> {
> cout << "op =1\n";
> if (this == &other) {
> return *this;
> } // if
>
> if (value != NULL) {
> delete[] value;
> } // if
>
> if (other.value == NULL) {
> value = NULL;
> } else {
> int length = strlen(other.value);
> value = new char[length + 1];
> strcpy(value, other.value);
> }
> return *this;
> }


Into:

MyString& MyString:perator=(MyString other)
{
value = other.value;
other.value = 0;
cout << "op =1\n";
return *this;
}

the output doesn't change, but now the "op=1" operation is quite
inexpensive.

--
gpd
 
Reply With Quote
 
 
 
 
Yannick Tremblay
Guest
Posts: n/a
 
      04-03-2008
In article <6a853$47f49214$3e024b42$(E-Mail Removed)>,
Christian Meier <chris@no_spam.com> wrote:
>


Deleted String class too complex to best illustrate NVRO.
All you need is a very simply class the cout
Constuctor/Copyconstructor/operator=. All the rest obfuscate.

>
>int main()
>{
> // First test case where the out parameter is faster.
> cout << "Before returnByVal()\n";
> MyString str1;


You create the object using the default constructor for no reason.
If you tell the compiler that it must create a default initialised
object then that is what it does even if a human code reviewer would
question why you felt the need to default initialise it and
immediately assign to it. The compiler can't send you an email
asking: "Are you sure that's really what you want to do?"

> str1 = returnByVal();


then assign to it

> cout << "\nBefore returnByParam()\n";
> MyString str2;
> returnByParam(str2);
> cout << "Finished test 1\n\n\n";
>
> // Second test case where return by value is faster.
> cout << "Before returnByVal()\n";
> MyString str3 = returnByVal();


Here you create and initialise correctly all at once. That's better

> cout << "\nBefore returnByParam()\n";
> MyString str4;
> returnByParam(str4);
> cout << "Finished test 2\n";
>}
>


>The output is the following:
>
>Before returnByVal()
>ctor 0
>ctor 1
>op =1
>
>Before returnByParam()
>ctor 0
>op =0
>Finished test 1
>
>
>Before returnByVal()
>ctor 1


And your test shows that there are less methods called.

>Before returnByParam()
>ctor 0
>op =0
>Finished test 2
>
>
>In the first test case the "out-parameter" version is faster.
>In the second test case the "return by value" version is faster.


Hmm, maybe. You have only traced number of methods called. Not speed
so you are assuming that this is faster. It might not be and it might
not be relevant.

Yan
 
Reply With Quote
 
Christian Meier
Guest
Posts: n/a
 
      04-03-2008
>>
>>int main()
>>{
>> // First test case where the out parameter is faster.
>> cout << "Before returnByVal()\n";
>> MyString str1;

>
> You create the object using the default constructor for no reason.
> If you tell the compiler that it must create a default initialised
> object then that is what it does even if a human code reviewer would
> question why you felt the need to default initialise it and
> immediately assign to it. The compiler can't send you an email
> asking: "Are you sure that's really what you want to do?"


It was my intension to do this in my first test case.
I could rewrite it to something like this:

MyString str1 = "Hello";
str1 += getWorldStringAsReturnValue();

It is not unusual that you have an already created string and you assign
something new (a return value of a function) to it.
I tried to cover this case by my first test.


>> Test case 1:
>>
>> Before returnByVal()
>> ctor 0
>> ctor 1
>> op =1
>>
>> Before returnByParam()
>> ctor 0
>> op =0
>> Finished test 1



>>In the first test case the "out-parameter" version is faster.
>>In the second test case the "return by value" version is faster.

>
> Hmm, maybe. You have only traced number of methods called. Not speed
> so you are assuming that this is faster. It might not be and it might
> not be relevant.
>
> Yan




"op =1" has quite the same statements as "op =0" except the additional test
in "op =1". I'm pretty sure that "op =0" is faster than "ctor 1" + "op =1".
But I agree that the speed difference might not be relevant.


Regards,
Chris


 
Reply With Quote
 
Christian Meier
Guest
Posts: n/a
 
      04-03-2008
> Change this:
>
>>
>> MyString& MyString:perator=(const MyString& other)
>> {
>> cout << "op =1\n";
>> if (this == &other) {
>> return *this;
>> } // if
>>
>> if (value != NULL) {
>> delete[] value;
>> } // if
>>
>> if (other.value == NULL) {
>> value = NULL;
>> } else {
>> int length = strlen(other.value);
>> value = new char[length + 1];
>> strcpy(value, other.value);
>> }
>> return *this;
>> }

>
> Into:
>
> MyString& MyString:perator=(MyString other)
> {
> value = other.value;
> other.value = 0;
> cout << "op =1\n";
> return *this;
> }
>
> the output doesn't change, but now the "op=1" operation is quite
> inexpensive.



This would decrease performance of the statements

MyString str5("asdf");
MyString str6("qwert");
str 5 = str6;

because of the copy constructor call which is necessary for the "by
value"-parameter.

Regards,
Chris


 
Reply With Quote
 
gpderetta
Guest
Posts: n/a
 
      04-03-2008
On Apr 3, 1:26 pm, "Christian Meier" <chris@no_spam.com> wrote:
> > Change this:

>
> >> MyString& MyString:perator=(const MyString& other)
> >> {
> >> cout << "op =1\n";
> >> if (this == &other) {
> >> return *this;
> >> } // if

>
> >> if (value != NULL) {
> >> delete[] value;
> >> } // if

>
> >> if (other.value == NULL) {
> >> value = NULL;
> >> } else {
> >> int length = strlen(other.value);
> >> value = new char[length + 1];
> >> strcpy(value, other.value);
> >> }
> >> return *this;
> >> }

>
> > Into:

>
> > MyString& MyString:perator=(MyString other)
> > {
> > value = other.value;
> > other.value = 0;
> > cout << "op =1\n";
> > return *this;
> > }

>
> > the output doesn't change, but now the "op=1" operation is quite
> > inexpensive.

>
> This would decrease performance of the statements
>
> MyString str5("asdf");
> MyString str6("qwert");
> str 5 = str6;
>
> because of the copy constructor call which is necessary for the "by
> value"-parameter.


No, it would be exaclty the same, because you need to do a copy
anyways.

With your implementation you do it in operator=, with mine you do it
in the constructor, so it is exactly the same number of copies.

Of course the real problem is that my implementation of operator=
leaks memory. Here is the correct one:

MyString& MyString:perator=(MyString other)
{
std::swap(value, other.value);
cout << "op =1\n";
return *this;
}

A better way would be to implement MyString::swap and implement
operator= in term of swap.

--
gpd
 
Reply With Quote
 
Yannick Tremblay
Guest
Posts: n/a
 
      04-03-2008
In article <642ba$47f4bdfa$3e024b42$(E-Mail Removed)>,
Christian Meier <chris@no_spam.com> wrote:
>>>
>>>int main()
>>>{
>>> // First test case where the out parameter is faster.
>>> cout << "Before returnByVal()\n";
>>> MyString str1;

>>
>> You create the object using the default constructor for no reason.
>> If you tell the compiler that it must create a default initialised
>> object then that is what it does even if a human code reviewer would
>> question why you felt the need to default initialise it and
>> immediately assign to it. The compiler can't send you an email
>> asking: "Are you sure that's really what you want to do?"

>
>It was my intension to do this in my first test case.
>I could rewrite it to something like this:
>
>MyString str1 = "Hello";
>str1 += getWorldStringAsReturnValue();
>
>It is not unusual that you have an already created string and you assign
>something new (a return value of a function) to it.
>I tried to cover this case by my first test.


Yes that is true. However, how would you write the above with:
void getWorldStringAsParam( MyString & str)

Euh???

MyString str1 = "Hello";
MyString tmp;
getWorldStringAsParam(tmp);
str1 += tmp;

Looks a lot worse to me. Unless you start changing the interface for a
special case one like:
void appendWorldStringToExistingStringParam(MyString & str)

but that seems even worse to me.

Yan



 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Zero Optimization and Sign Optimization??? Ravikiran C Programming 22 11-24-2008 03:19 AM
Is a copy constructor needed for the return value optimization? SzH C++ 10 04-26-2007 02:45 PM
what value does lack of return or empty "return;" return Greenhorn C Programming 15 03-06-2005 08:19 PM
Q: Return value optimization thru several function calls? Denis Remezov C++ 20 04-20-2004 04:15 PM
Re: return value optimization on expression/meta templates E. Robert Tisdale C++ 0 06-25-2003 04:54 AM



Advertisments