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

我正在回答 一个问题并推荐按值返回大类型 因为我相信编译器会执行 返回值优化 (RVO).但是后来有人向我指出 Visual Studio 2013 没有对我的代码执行 RVO.

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.

我发现 这里有一个问题关于 Visual Studio 未能执行 RVO,但在这种情况下,结论似乎是如果真的很重要,Visual Studio 将执行 RVO.就我而言,它确实很重要,它对性能产生了重大影响,我已经通过分析结果确认了这一点.这是简化的代码:

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

我期望编译器对 getBigFoo() 的返回类型执行 RVO.但它似乎是在复制 Foo.

I'm expecting the compiler to perform RVO on the return type from getBigFoo(). But it appears to be copying Foo instead.

我知道编译器将为创建一个复制构造函数福.我还知道,与兼容的 C++11 编译器不同,Visual Studio 不会为 Foo.但这应该没问题,RVO 是一个 C++98 概念,无需移动语义即可工作.

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.

那么,问题是,在这种情况下,Visual Studio 2013 是否有充分的理由不执行返回值优化?

So, the question is, is there a good reason why Visual Studio 2013 does not perform return value optimization in this case?

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

I know of a few workarounds. I can define a move-constructor for Foo:

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

这很好,但是有很多遗留类型没有移动构造函数,很高兴知道我可以依靠 RVO 处理这些类型.此外,某些类型可能天生可复制但不可移动.

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.

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

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;

这很奇怪,因为我认为 NVRO 不如 RVO 可靠.

which is curious because I thought NVRO was less reliable than RVO.

更奇怪的是,如果我更改 Foo 的构造函数,以便它创建并填充 vector:

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
  }

当我尝试执行 RVO 时,它没有将其移入,而是有效:

instead of moving it in then when I try to do RVO, it works:

Foo getBigFoo() {
  return Foo(1000000);
}

我很高兴采用其中一种解决方法,但我希望能够预测 RVO 将来何时可能会像这样失败,谢谢.

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.

更简洁的现场演示来自@dyp

Edit2:我为什么不直接写return v;?

首先,它没有帮助.Profiler 结果显示,如果我只编写 return v; ,Visual Studio 2013 仍然会复制向量,即使它确实有效,也只是一种解决方法.我并不是要真正修复这段特定的代码,而是试图了解 RVO 失败的原因,以便我可以预测将来它何时可能会失败.确实,这是编写此特定示例的更简洁的方法,但是在很多情况下我不能只编写 return v;,例如 if Foo有额外的构造函数参数.

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.

推荐答案

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

If the code looks like it should be optimized, but is not getting optimized I would submit bug here http://connect.microsoft.com/VisualStudio or raise a support case with Microsoft. This article, although it is for VC++2005 (I couldn't find a current version of document) does explain some scenarios where it won't work. http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx#nrvo_cpp05_topic3

如果我们想确定优化已经发生,一种可能性是检查程序集输出.如果需要,这可以作为构建任务自动化.

If we want to be sure the optimization has occurred, one possibility is to check the assembly output. This could be automated as a build task if desired.

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

This requires generating .asm output using /FAs option like so:

cl test.cpp /FAs

将生成 test.asm.

Will generate test.asm.

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

A potential example in PowerShell below, which can be used in this way:

PS C:	est> .Get-RVO.ps1 C:	est	est.asm test.cpp
NOT RVO test.cpp - ; 13   :   return Foo(std::move(v));// Expecting RVO to happen here.

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

PS C:	est> 

脚本:

# Usage Get-RVO.ps1 <input.asm file> <name of CPP file you want to check>
# Example .Get-RVO.ps1 C:	est	est.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"
                }
            }
        }
    }

}

相关文章