Return value optimization

Discussion in 'C++' started by Christian Meier, Apr 3, 2008.

  1. 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::eek: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::eek: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.
     
    Christian Meier, Apr 3, 2008
    #1
    1. Advertising

  2. Christian Meier

    gpderetta Guest

    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::eek: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::eek: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
     
    gpderetta, Apr 3, 2008
    #2
    1. Advertising

  3. In article <6a853$47f49214$3e024b42$>,
    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
     
    Yannick Tremblay, Apr 3, 2008
    #3
  4. >>
    >>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
     
    Christian Meier, Apr 3, 2008
    #4
  5. > Change this:
    >
    >>
    >> MyString& MyString::eek: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::eek: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
     
    Christian Meier, Apr 3, 2008
    #5
  6. Christian Meier

    gpderetta Guest

    On Apr 3, 1:26 pm, "Christian Meier" <chris@no_spam.com> wrote:
    > > Change this:

    >
    > >> MyString& MyString::eek: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::eek: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::eek: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
     
    gpderetta, Apr 3, 2008
    #6
  7. In article <642ba$47f4bdfa$3e024b42$>,
    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
     
    Yannick Tremblay, Apr 3, 2008
    #7
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. E. Robert Tisdale
    Replies:
    0
    Views:
    832
    E. Robert Tisdale
    Jun 25, 2003
  2. Denis Remezov
    Replies:
    20
    Views:
    723
    Jakob Bieling
    Apr 20, 2004
  3. Greenhorn
    Replies:
    15
    Views:
    827
    Keith Thompson
    Mar 6, 2005
  4. SzH
    Replies:
    10
    Views:
    651
    James Kanze
    Apr 26, 2007
  5. Ravikiran

    Zero Optimization and Sign Optimization???

    Ravikiran, Nov 17, 2008, in forum: C Programming
    Replies:
    22
    Views:
    866
    Thad Smith
    Nov 24, 2008
Loading...

Share This Page