为什么Visual Studio在此情况下不执行返回值优化(RVO) [英] Why does Visual Studio not perform return value optimization (RVO) in this case

查看:1660
本文介绍了为什么Visual Studio在此情况下不执行返回值优化(RVO)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我回答了问题,并建议返回大值类型的值,因为我相信编译器将执行返回值优化( RVO)。但是后来有人指出,Visual Studio 2013没有对我的代码执行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); //用不重要的数据填充向量

return Foo(std :: move(v)); //期望RVO发生在这里。
}

int main(){
std :: cout< 按任意键开始测试...;
std :: cin.ignore();

for(int i = 0; i!= 100; ++ i){//重复测试以获得有意义的剖析器结果
auto foo = getBigFoo();
std :: cout<< std :: accumulate(foo.v.begin(),foo.v.end(),0.0)< \\\
;
}
}



我期望编译器对从 getBigFoo()返回类型。但是它似乎是复制 Foo



我知道编译器将为 Foo 创建一个复制构造函数。我也知道,与兼容的C ++ 11编译器不同, Visual Studio不会为<$​​创建移动构造函数 c $ c> Foo 。但是这应该是OK,RVO是一个C ++ 98的概念,没有移动语义。



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



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

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

是好的,但有很多遗留类型,没有移动构造函数,很高兴知道我可以依靠RVO与这些类型。



如果我从RVO更改为NVRO(名为返回值优化),则Visual Studio 会执行显示执行优化:

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

这很好奇,因为我认为NVRO比RVO的可靠性要低。 / p>

更奇怪的是如果我改变 Foo 的构造函数,所以它创建并填充向量

  Foo(size_t num):v(num){
std: :iota(v.begin(),v.end(),0); //使用非平凡数据填充向量
}

我尝试做RVO,它的工作原理:

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



我很高兴与其中一个解决方法,但我想感谢。



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



Edit2:为什么我不写 code> return v; ?



一开始,它没有帮助。分析器结果显示,如果我只写 return v; ,Visual Studio 2013仍然复制向量;即使它工作,它只会是一个解决方法。我不想实际修复这个特定的代码,我试图理解为什么RVO失败,所以我可以预测,它可能会在未来失败。确实,这是一个更简洁的方式写这个特定的例子,但有很多情况下,我不能写 return v; ,例如如果 Foo 有额外的构造函数参数。

解决方案

如果代码看起来像已优化,但未得到优化我将在此处提交错误 http://connect.microsoft.com/VisualStudio 或向Microsoft提出支持案例。
这篇文章,虽然它是为VC ++ 2005(我找不到当前版本的文档)解释一些情况下,它将无法工作。 http://msdn.microsoft.com/ en-us / library / ms364057(v = vs.80).aspx#nrvo_cpp05_topic3



如果我们想确保优化已发生,是检查装配输出。



这需要使用/ 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)); //期望RVO发生在这里。

PS C:\test> .\Get-RVO.ps1 C:\test\test_v2.optimized.asm test.cpp
RVO OK test.cpp - ; 13:return {std :: move(v)}; //期望RVO发生在这里。

PS C:\test>

脚本:

 #Usage Get-RVO.ps1< input.asm file> <您要检查的CPP文件的名称> 
#示例.\Get-RVO.ps1 C:\test\test.asm test.cpp
[CmdletBinding()]
Param(
[参数= $ True,Position = 1)]
[string] $ assemblyFilename,

[参数(必填= $ True,位置= 2) b $ b)

$ sr = New-Object System.IO.StreamReader($ assemblyFilename)
$ IsInReturnSection = $ false
$ optimized = $ true
$ startLine =
$ inFile = $ false

while(!$ sr.EndOfStream)
{
$ line = $ sr.ReadLine

#忽略不是我们指定的CPP文件的任何文件
if($ line.StartsWith(; File))
{
if .EndsWith($ cppFilename))
{
$ inFile = $ true
}
else
{
$ inFile = $ false
}
}

#检查我们是否在我们的CPP文件的代码部分...
if($ inFile)
{
if($ line .startsWith(;))
{
#标记开始的返回代码
#假设已优化,未验证否则
if($ line.Contains(return ))
{
$ startLine = $ line
$ IsInReturnSection = $ true
$ optimized = $ true
}
}

if($ IsInReturnSection)
{
#返回节中的调用,而不是RVO
if($ line.Contains(call))
{
$ ($ line.StartsWith($)-or $ line.StartsWith($)$ $ $ $ $




?))
{
$ IsInReturnSection = $ false
if($ optimized)
{
RVO OK $ cppfileName - $ startLine
}
else
{
NOT RVO $ cppfileName - $ startLine
}
}
}
}
$ b b}


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

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.

解决方案

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.

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

cl test.cpp /FAs

Will generate test.asm.

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

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> 

The script:

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

}

这篇关于为什么Visual Studio在此情况下不执行返回值优化(RVO)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆