【发布时间】:2014-11-15 19:20:01
【问题描述】:
我回答a question 并推荐return by-value for a large type,因为我相信编译器会执行return-value optimization (RVO)。但后来有人向我指出,Visual Studio 2013 没有在我的代码上执行 RVO。
我发现 a question here 关于 Visual Studio 无法执行 RVO,但在这种情况下,结论似乎是,如果真的很重要,Visual Studio 将执行 RVO。就我而言,它确实很重要,它会对性能产生重大影响,我已经通过分析结果证实了这一点。这是简化的代码:
#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) << "\n";
}
}
我希望编译器对来自getBigFoo() 的返回类型执行 RVO。但它似乎是在复制Foo。
我知道 will create a copy-constructor 的编译器 Foo。我也知道,与 Foo 的兼容 C++11 编译器 Visual Studio does not create a move-constructor 不同。但这应该没问题,RVO 是一个 C++98 概念,无需移动语义即可工作。
那么,问题是,Visual Studio 2013 在这种情况下不执行返回值优化是否有充分的理由?
我知道一些解决方法。我可以为Foo 定义一个移动构造函数:
Foo(Foo&& in) : v(std::move(in.v)) {}
这很好,但是有很多遗留类型没有移动构造函数,很高兴知道我可以依赖 RVO 来处理这些类型。此外,某些类型可能本质上是可复制的,但不可移动。
如果我从 RVO 更改为 NVRO(命名返回值优化),那么 Visual Studio 确实 似乎会执行优化:
Foo foo(std::move(v))
return foo;
这很好奇,因为我认为 NVRO 的可靠性不如 RVO。
更奇怪的是,如果我更改Foo 的构造函数,那么它会创建并填充vector:
Foo(size_t num) : v(num) {
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
}
当我尝试做 RVO 时,它没有将其移入,而是有效:
Foo getBigFoo() {
return Foo(1000000);
}
我很高兴采用其中一种解决方法,但我希望能够预测 RVO 将来何时可能会像这样失败,谢谢。
编辑:来自@dyp的More concise live demo
Edit2:我为什么不直接写return v;?
首先,它没有帮助。 Profiler 结果显示,如果我只写return v;,Visual Studio 2013 仍会复制向量,即使它确实有效,也只是一种解决方法。我并没有试图真正修复这段特定的代码,我试图理解 RVO 失败的原因,以便我可以预测它将来何时可能失败。确实,这是编写这个特定示例的一种更简洁的方式,但在很多情况下我不能只写 return v;,例如,如果 Foo 有额外的构造函数参数。
【问题讨论】:
-
当然,您可以使用
return {std::move(v)};,因为该构造函数不是显式的。这不需要任何 (N)RVO,它指定不创建临时。 -
你为什么不直接写
return v;? -
我刚刚在 Visual Studio 2014 CTP 上尝试过,它为您的代码应用了 RVO。编辑:我应该说@dyp的例子。
-
我在此处发布了一些有关何时执行 RVO 以及何时失败(基于来自 @dyp 的示例)的详细信息:rovrov.com/blog/2014/11/21/RVO-and-copy-elision-failing。这并不能解释为什么 RVO 会失败,但一些观察结果可能仍然很有趣。
标签: c++ c++11 visual-studio-2013 move-semantics rvo