C++: Return Value versus Output Parameter

The C++ programmer has to be constantly aware of the performance of the code he is writing. One conundrum that arises often is with the output of a function. If this output is an object of substantial size, is it better to return it as the return value or as an output parameter?

Question: Which of these functions is faster?

Foo Function1();
void Function2( Foo& );

Answer: It depends. Typically it is Function2, sometimes both are comparable.

  • Function1 looks elegant, both for a programmer and for a mathematician. However, it breaks down as soon as one wants to return multiple values from a function. One could use modern C++ constructs like pair or tuple, but that is extra machinery.
  • Function2 does not look as elegant as Function1. One needs to be careful here to pass the output parameter as a reference (or pointer) parameter. However, this format can handle multiple output parameters easily.
  • Returning built-in types (like integer) or tiny objects are easy for the C++ compiler to optimize. They should be equally fast with both Function1 and Function2.
  • Function1 typically needs to construct the object it is returning. Depending on the class of the object, this might take substantial amount of time. After construction, it may need to modify or fill the object with data.
    Foo Function1()
    {
        Foo foo; // Construction cost
        foo.fillSomething();
        return foo;
    }
    
  • Function2 assumes that the parameter object is already created. It only modifies or fills the object with data.
    void Function1( Foo& foo)
    {
        foo.fillSomething();
        return;
    }
    
  • The performance also depends on what is happening in the caller. The caller might be assigning the return value to initialize a new object or to an already existing object.
    int main()
    {
        // New object
        Foo f1 = Function1();
        Foo f2;
        Function2( f2 );
    
        // Existing object
        Foo f;
        while ( x )
        {
            // Do something
            f = Function1();
            // Do something
            f.clear();
            Function2( f );
        }
    
        return 0;
    }
    
  • Function1 involves one object creation inside the function and one copy of this object to the destination object. That looks very expensive! However, all modern C++ compilers perform Return Value Optimization (RVO) that eliminates the expensive copy operation.
  • Note that RVO cannot always be applied by the compiler. When this happens, Function1 becomes all the more expensive.

I conducted a simple test using the STL vector as the return object. The code used for this can be seen here. The code was compiled using the Visual C++ 2010 compiler (16.00.30319.01) in Release mode.

New object ...
Function1: 1.753
Function2: 1.615

Existing object ...
Function1: 1.671
Function2: 1.035

The results clearly show that output parameter is always faster. When assigning to a new object, the speed difference is negligible. However, when reusing an existing object, the speed difference is substantial! πŸ™‚

Advertisements

6 thoughts on “C++: Return Value versus Output Parameter

  1. You should run the same test using the gcc compiler (g++) with the -S option, which will give you the assembly. This way is better because it’s more accurate as you can see which way will generate more instructions.

    Of course, it’ll differ from a compiler to other, but perhaps vc++ also has an option like this for comparison purposes.

    Like

    1. Carlos: The assembly code can be seen with the compiler option /FAs in Visual C++. However, C++ generates far more noisy assembly, making the examination of the assembly quite hard. πŸ™‚

      Like

  2. I see no difference in the code in the Function 2 section of “new object” and “Existing object”.

    In the “New object” case,

    The IntVec ivec2; is inside the block of “begin2” and “end2” and

    In the “Existing object” case,

    The IntVec ivec3; is outside the block of “begin2” and “end2”

    Just adding a construction of vector is making a difference of “0.5” seconds!

    I believe the reason for such a vast difference might be some thing else. Can you please explain ?

    Like

    1. Mak: I do not see any other reason. Constructing a vector is quite expensive. It involves memory allocation on the heap.

      Most large real-world objects will involve memory allocation on the heap, so this assumption is quite valid.

      Like

  3. Bringing it back from dead…

    Would it be right to say that while:
    a) a new vector has its memory space allocated while its being filled up (push_back);
    b) the existing vector had its memory actually allocated only once (first time it’s filled up with new elements), and when it’s flushed (clear), that memory blocks were merely marked as “free”, but kept for future use?

    This would be the only explanation to make me accept this performance difference. Off course, I’m not talking about the out parameter, that I’m sure has the best performance. If you think of a vector as a return value (like Function1), you are forcing the creation of one vector outside the function and another inside the function (for each time you call the function), as well as a copy of the contents from one to another (for each function call), this process will potentially lead to a big performance penalty.

    I would really know which optimizations a compiler does to avoid this. For example, if you return a std::string, maybe only the char pointer could be assigned to the outer string, as the returning one won’t be used anymore. Are there such optimizations? In what situations?

    Like

    1. Iuri: Your statements about vector should be correct for most vector implementations.

      You have highlighted some interesting optimizations that might be implemented in C++ compilers. While return value optimization is very popular, I have not heard about any of these other types of optimizations.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.