Boost logo

Boost :

From: Terje Slettebø (tslettebo_at_[hidden])
Date: 2002-10-13 11:08:42


>From: "Daniel Frey" <d.frey_at_[hidden]>

>On Sat, 12 Oct 2002 22:28:01 +0200, Terje Slettebø wrote:

>> I've done some preliminary testing (only tested on one compiler, Intel
>> C++ 7.0 beta), to test this hypothesis, to test the various ways of
>> implementing operator+(). I made the following test program:

>(Note that the Intel C++ 6 had a bug in it's NRVO which prevented a NRV of
>type T in a function which returns const T prevented the NRVO from being
>applied. I already told Intel and it's in their pipeline, but I have not
>yet received a notification that the issue is solved. Maybe it's already
>corrected in the v7)

Unfortunately, no, at least not in the current 7.0 beta. You have to use the
ugly const_cast workaround, or it will make an extra copy:

inline const Test operator+(const Test &t1,const Test &t2)
{
  const Test nrv(t1);
  *const_cast<Test *>(&nrv)+=t2;
  return nrv;
}

 I checked this now.

>> class Test
>> {
>> public:
>> Test(int n) : num(n) {}
>>
>> Test &operator+=(const Test &other)
>> {
>> num+=other.num;
>>
>> return *this;
>> }
>>
>> int num;
>> int array[1024]; // Just so that copying shows up
>> };

>You might want to place some cout's in the ctor/dtor to see when objects
>are created and destroyed. This is also important as it prevent
>assembler-level optimizations and shows what *object* are really optimized
>away.

I was thinking of that. However, I wanted to make it a simple object, to
increase the chances of the RVO/NRVO being applied. Is it so that RVO can't
be applied if there may be a side effect, whereas NRVO can be applied,
regardless? Having cout's in the copy constructor would certainly constitute
a side effect.

Could someone point me to the part of the standard that allows for RVO and
NRVO?

I tested this now, using the NRVO version of operator+() (the version above
here), with a cout in the copy constructor. Only one copy was made in
main(), as before, the required one to construct the return object. Yay. :)

>> Test test_num1(1);
>> Test test_num2(2);
>> int num;
>> int array;
>>
>> int main()
>> {
>> Test test_num=test_num1+test_num2;

>It is also important to test expressions like x = a+b+c and x = a+(b+c) to
>see the real difference of taking parameters by value :) Especially the
>difference for taking the first or the second parameter by value...

Alright. I just did a simple test, and posted the code, so people might
check it, as well. We can discuss theory all we want, but to find out how it
goes when the rubber meets the road, there's no substitute for real code. :)

Anyway, I tested your suggestion using a larger set of compilers, and here's
the result (2 copies are the fewest you can expect, without using expression
templates, transforming the expression to eliminate the temporary from the
sub-expression):

                                                a+b+c a+(b+c)
----------------------------------------------------------------------------

--
NRV (Intel C++)                     2 copies    2 copies
Call by value (Intel C++)        4 copies    4 copies
NRV (MSVC 6)                     4 copies     4 copies
Call by value (MSVC 6)       3 copies     4 copies (*)
NRV (g++ 2.95.3)                 4 copies     4 copies
Call by value (g++ 2.95.3)    3 copies     4 copies (*)
----------------------------------------------------------------------------
--
(*) You're right about the difference of taking the first or second
parameter by value, as shown here.
It is likely that newer compilers will fare better when it comes to NRVO, as
it gets implemented on more compilers.
>> It seems that, at least for this compiler, Andrei's suggestion to pass
>> by value if you need to make a copy, anyway, resulted in less optimised
>> code. Considering that, in that case, it has to make a copy, to call the
>> function, then it's already too late to use the NRVO in the function, as
>> it's already a copy, so the above results makes sense.
>The point IMHO is, that taking the parameter by value may lead to equally
>optimized code for *some* cases.
True, or more, as shown above, depending on the compiler.
>For the general case, only the NRVO may
>lead to optimized code for all cases. And a function which takes a const
>T& and makes a copy of it is IMHO not lying. If it makes a copy, it's an
>implementation detail.
I agree.
>I have seen implementation of operator+ which don't
>make a copy of the arguments, but why should all these details be
>reflected in the function's signature?
>> To quote again from above:
>>
>>> Taking const& T
>>> as arguments in /any/ function when you actually *do* need a copy
>>> chokes
>> the
>>> compiler (and Zuto) and practically forbids them to make important
>>> optimizations.
>>
>> At least for Intel C++, this turns out to be the other way around.
>> Calling by value prevents the NRVO.
>Yes. And it's not limited to the Intel C++, as the standard itself
>requires compilers to behave this way. A compiler is basically allowed to
>remove temporaries only if it can figure out that this does not have any
>observable side effects.
However, that's not the case for NRV, as mentioned, is it? After all, I got
a varying amount of cout printing, depending on whether or not NRV was used.
What I'm wondering is, could a value parameter be considered an NRV by the
compiler, or does it have to be created explicitly in the function?
>And I have never seen any compiler which is smart
>enough to do this for objects like the above 'Test'-objects. Or if their
>are special rules which allows to remove temporaries even if there are
>observable side effects. This is the reason why I think it is important to
>apply the NRVO as it can do an optimization that the compiler cannot
>figure out itself.
I agree.
Regards,
Terje

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk