Chr*_*rew 41 c++ move-semantics rvo c++11 visual-studio-2013
我正在回答一个问题并建议为大型类型返回按值,因为我相信编译器会执行返回值优化(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";
}
}
Run Code Online (Sandbox Code Playgroud)
我期待编译器在返回类型上执行RVO getBigFoo().但它似乎是复制而已Foo.
我知道,编译器将创建一个拷贝构造函数的Foo.我也知道,不像一个标准的C++编译器11 的Visual Studio不创建移动构造函数的Foo.但这应该没问题,RVO是一个C++ 98概念,并且没有移动语义.
所以,问题是,在这种情况下,Visual Studio 2013是否有理由不执行返回值优化?
我知道一些解决方法.我可以为以下内容定义一个move-constructor Foo:
Foo(Foo&& in) : v(std::move(in.v)) {}
Run Code Online (Sandbox Code Playgroud)
这很好,但有很多遗留类型没有移动构造函数,很高兴知道我可以依赖这些类型的RVO.此外,某些类型可能是固有可复制的但不可移动的.
如果我从RVO更改为NVRO(命名返回值优化),那么Visual Studio 似乎确实执行了优化:
Foo foo(std::move(v))
return foo;
Run Code Online (Sandbox Code Playgroud)
这是奇怪的,因为我认为NVRO是少比RVO可靠.
更奇怪的是,如果我更改构造函数,Foo那么它会创建并填充vector:
Foo(size_t num) : v(num) {
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
}
Run Code Online (Sandbox Code Playgroud)
当我尝试做RVO时,而不是移动它,它的工作原理:
Foo getBigFoo() {
return Foo(1000000);
}
Run Code Online (Sandbox Code Playgroud)
我很高兴能够采用其中一种解决方法,但我希望能够预测RVO将来可能会失败,谢谢.
编辑: 来自@dyp的更简洁的现场演示
Edit2:我为什么不写return v;?
首先,它没有帮助.Profiler结果显示,如果我只是编写,Visual Studio 2013仍会复制矢量return v;.即使它确实有效,它也只是一种解决方法.我并没有试图修复这段特殊的代码,我试图理解为什么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
Run Code Online (Sandbox Code Playgroud)
将生成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>
Run Code Online (Sandbox Code Playgroud)
剧本:
# 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"
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4198 次 |
| 最近记录: |