I was answering a question and recommending return by-value for a large type because I was confident the compiler would perform return-value optimization (RVO). But then it was pointed out to me that Visual Studio 2013 was not performing RVO on my code.
I've found a question here regarding Visual Studio failing to perform RVO but in that case the conclusion seemed to be that if it really matters Visual Studio will perform RVO. In my case it does matter, it makes a significant impact to performance which I've confirmed with profiling results. Here is the simplified code:
#include <vector>
#include <numeric>
#include <iostream>
struct Foo {
std::vector<double> v;
Foo(std::vector<double> _v) : v(std::move(_v)) {}
};
Foo getBigFoo() {
std::vector<double> v(1000000);
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
return Foo(std::move(v)); // Expecting RVO to happen here.
}
int main() {
std::cout << "Press any key to start test...";
std::cin.ignore();
for (int i = 0; i != 100; ++i) { // Repeat test to get meaningful profiler results
auto foo = getBigFoo();
std::cout << std::accumulate(foo.v.begin(), foo.v.end(), 0.0) << "
";
}
}
I'm expecting the compiler to perform RVO on the return type from getBigFoo()
. But it appears to be copying Foo
instead.
I'm aware that the compiler will create a copy-constructor for Foo
. I'm also aware that unlike a compliant C++11 compiler Visual Studio does not create a move-constructor for Foo
. But that should be OK, RVO is a C++98 concept and works without move-semantics.
So, the question is, is there a good reason why Visual Studio 2013 does not perform return value optimization in this case?
I know of a few workarounds. I can define a move-constructor for Foo
:
Foo(Foo&& in) : v(std::move(in.v)) {}
which is fine, but there are a lot of legacy types out there that don't have move-constructors and it would be nice to know I can rely on RVO with those types. Also, some types may be inherently copyable but not movable.
If I change from RVO to NVRO (named return value optimization) then Visual Studio does appear to perform the optimization:
Foo foo(std::move(v))
return foo;
which is curious because I thought NVRO was less reliable than RVO.
Even more curious is if I change the constructor of Foo
so it creates and fills the vector
:
Foo(size_t num) : v(num) {
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
}
instead of moving it in then when I try to do RVO, it works:
Foo getBigFoo() {
return Foo(1000000);
}
I'm happy to go with one of these workarounds but I'd like to be able to predict when RVO might fail like this in the future, thanks.
Edit: More concise live demo from @dyp
Edit2: Why don't I just write return v;
?
For a start, it doesn't help. Profiler results show that Visual Studio 2013 still copies the vector if I just write return v;
And even if it did work it would only be a workaround. I'm not trying to actually fix this particular piece of code, I'm trying to understand why RVO fails so I can predict when it might fail in the future. It is true that it is a more concise way of writing this particular example but there are plenty of cases where I couldn't just write return v;
, for example if Foo
had additional constructor parameters.
See Question&Answers more detail:
os