2014-09-21 57 views
40

我回答了a question并建议使用return by-value for a large type,因为我确信编译器会执行return-value optimization (RVO)。但后来有人向我指出,Visual Studio 2013未在我的代码上执行RVO。为什么Visual Studio在这种情况下不执行返回值优化(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-constructorFoo。我也知道,与符合C++ 11编译器Visual Studio does not create a move-constructorFoo不同。但是,这应该是确定的,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何时可能会在未来发生此类故障,谢谢。

编辑:More concise live demo从@dyp

EDIT2:我为什么不只是写return v;

首先,它没有帮助。事件探查器结果显示,如果我只写return v;,Visual Studio 2013仍会复制向量,即使它工作,它也只是一种解决方法。我并没有试图修复这段代码,我想了解RVO为什么会失败,所以我可以预测它何时可能会失败。确实,这是编写这个特定示例的更简洁的方式,但是有很多情况下我不能仅仅编写return v;,例如,如果Foo具有其他构造函数参数。

+0

在黑暗中总刺:你观察到不同的行为,如果你使用'返回Foo(std :: move(v));'(圆括号)而不是'return Foo {std :: move(v)};'(花括号)?我不认为这会有所作为,但我不愿意在这方面下注。 – 2014-09-21 20:53:26

+0

@Insilico,不,同样的行为,我只在那里使用花括号(统一初始化),因为它有点过于接近最令人烦恼的解析。我会改变它,除非抛出任何人... – 2014-09-21 20:57:41

+2

当然,你可以使用'return {std :: move(v)};'因为这个构造函数不是显式的。这不需要任何(N)RVO,它被指定不创建临时。 – dyp 2014-09-21 21:00:34

回答

3

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

如果我们想确保优化已经发生,一种可能性是检查装配输出。如果需要,这可以作为构建任务自动执行。

这需要产生.ASM使用的FA选项输出/像这样:

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" 
       } 
      } 
     } 
    } 

} 
+0

我真的不想在每次想使用RVO时检查组件!如果这是必要的,我想我不会依赖RVO。我欣赏关于如何生成/检查程序集的说明。我已经证实了Jagannath的观察,它已在Visual Studio 2014 CTP中修复,因此提交错误可能没有意义。 – 2014-11-20 03:00:22

+0

这是100%确定的唯一可能的方式,因为即使您按照Microsoft链接中提到的逻辑进行操作,编译器错误可能会存在,或者您遇到了不明显的情况。但是,如果脚本非常重要,那么脚本可以用于自动检查构建后脚本。如果你真的想知道检查实际的逻辑,我想你必须使用开源编译器CLang或GCC。 – 2014-11-20 03:30:02

+0

这就是说,如果你有很好的记录,可重现的情况下,微软通常会回应错误报告,它可能会“按设计”结论,但至少你可能会得到一个理由。 – 2014-11-20 03:31:31

相关问题