为什么Visual Studio在这种情况下不执行返回值优化(RVO)

我正在回答一个问题,并build议按值返回一个大的types,因为我确信编译器会执行返回值优化(RVO) 。 但后来有人向我指出,Visual Studio 2013在我的代码上没有执行RVO。

我在这里发现了一个关于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()的返回types上执行RVO。 但它似乎是复制Foo

我知道编译器将为 Foo 创build一个复制构造函数 。 我也意识到,与符合C ++ 11编译器不同, Visual Studio不会为Foo 创build移动构造函数 。 但是,这应该是确定的,RVO是一个C + + 98的概念,工作没有移动语义。

所以,问题是,Visual Studio 2013在这种情况下为什么不执行返回值优化有一个很好的理由?

我知道几个解决方法。 我可以为Foo定义一个移动构造函数:

 Foo(Foo&& in) : v(std::move(in.v)) {} 

这是好的,但有很多传统的types没有移动构造函数,并且很高兴知道我可以依靠这些types的RVO。 另外,某些types可能固有地可复制但不可移动。

如果我从RVO更改为NVRO(命名返回值优化),那么Visual Studio似乎执行优化:

  Foo foo(std::move(v)) return foo; 

这很奇怪,因为我认为NVRO比RVO 更不可靠。

更奇怪的是,如果我改变Foo的构造函数,所以它创build并填充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 更简洁的现场演示

编辑2:为什么我不写return v;

一开始,这并没有帮助。 事件探查器结果显示,如果我只写了return v; ,Visual Studio 2013仍然复制向量return v; 即使这样做,它只会是一个解决方法。 我并没有试着去修正这段代码,我想知道RVO为什么会失败,所以我可以预测它将来何时会失败。 确实,这是一个更简洁的写这个例子的方法,但是有很多情况我不能只写return v; 例如,如果Foo有额外的构造函数参数。

如果代码看起来应该优化,但没有得到优化,我会在这里提交错误http://connect.microsoft.com/VisualStudio或提出与微软的支持案例。; 本文虽然是针对VC ++ 2005(我找不到当前版本的文档),但它解释了一些无法工作的场景。 http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx#nrvo_cpp05_topic3

如果我们想要确定已经发生了优化,则有一种可能性是检查assembly输出。 如果需要,这可以作为构build任务自动化。

这需要使用/ FAs选项生成.asm输出,如下所示:

 cl test.cpp /FAs 

会生成test.asm。

下面的PowerShell中的一个潜在的例子,可以这样使用:

 PS C:\test> .\Get-RVO.ps1 C:\test\test.asm test.cpp NOT RVO test.cpp - ; 13 : return Foo(std::move(v));// Expecting RVO to happen here. PS C:\test> .\Get-RVO.ps1 C:\test\test_v2.optimized.asm test.cpp RVO OK test.cpp - ; 13 : return {std::move(v)}; // Expecting RVO to happen here. PS C:\test> 

剧本:

 # Usage Get-RVO.ps1 <input.asm file> <name of CPP file you want to check> # Example .\Get-RVO.ps1 C:\test\test.asm test.cpp [CmdletBinding()] Param( [Parameter(Mandatory=$True,Position=1)] [string]$assemblyFilename, [Parameter(Mandatory=$True,Position=2)] [string]$cppFilename ) $sr=New-Object System.IO.StreamReader($assemblyFilename) $IsInReturnSection=$false $optimized=$true $startLine="" $inFile=$false while (!$sr.EndOfStream) { $line=$sr.ReadLine(); # ignore any files that aren't our specified CPP file if ($line.StartsWith("; File")) { if ($line.EndsWith($cppFilename)) { $inFile=$true } else { $inFile=$false } } # check if we are in code section for our CPP file... if ($inFile) { if ($line.StartsWith(";")) { # mark start of "return" code # assume optimized, unti proven otherwise if ($line.Contains("return")) { $startLine=$line $IsInReturnSection=$true $optimized=$true } } if ($IsInReturnSection) { # call in return section, not RVO if ($line.Contains("call")) { $optimized=$false } # check if we reached end of return code section if ($line.StartsWith("$") -or $line.StartsWith("?")) { $IsInReturnSection=$false if ($optimized) { "RVO OK $cppfileName - $startLine" } else { "NOT RVO $cppfileName - $startLine" } } } } }