web-dev-qa-db-ja.com

この場合、Visual Studioが戻り値の最適化(RVO)を実行しないのはなぜですか

私は 質問 と推奨していました 大きな型の値による戻り値 コンパイラが実行すると確信していたので 戻り値の最適化(RVO) =。しかし、その後、Visual Studio2013が私のコードでRVOを実行していないことが指摘されました。

Visual StudioがRVOを実行できないことに関して、 ここで質問 を見つけましたが、その場合、本当に重要な場合はVisualStudioがRVOを実行するという結論になりました。私の場合、それは does の問題であり、プロファイリングの結果で確認したパフォーマンスに大きな影響を与えます。簡略化されたコードは次のとおりです。

_#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をコピーしているようです。

コンパイラー コピーコンストラクターを作成します for Fooであることを私は知っています。また、準拠したC++ 11コンパイラとは異なり Visual Studioはmove-constructorを作成しません for Fooであることも認識しています。しかし、それは問題ないはずです。RVOはC++ 98の概念であり、移動セマンティクスなしで機能します。

それで、問題は、この場合、Visual Studio 2013が戻り値の最適化を実行しない正当な理由があるのでしょうか?

私はいくつかの回避策を知っています。 Fooのmove-constructorを定義できます。

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

これは問題ありませんが、move-constructorを持たないレガシータイプがたくさんあります。これらのタイプで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);
}
_

これらの回避策の1つを実行できてうれしいですが、RVOが将来このように失敗する可能性がある時期を予測できるようにしたいと思います。

編集:より簡潔なライブデモ @dypから

Edit2:なぜ私は_return v;_と書いてみませんか?

まず第一に、それは役に立ちません。プロファイラーの結果は、_return v;_と書くだけで、Visual Studio 2013がベクターをコピーすることを示しています。それが機能したとしても、回避策にすぎません。私はこの特定のコードを実際に修正しようとしているのではなく、RVOが失敗する理由を理解しようとしているので、将来いつ失敗するかを予測できます。この特定の例を書くのがより簡潔な方法であることは事実ですが、たとえばFooに追加のコンストラクターパラメーターがある場合など、_return v;_だけを書くことができない場合がたくさんあります。

41
Chris Drew

コードを最適化する必要があるように見えても、最適化されていない場合は、ここでバグを送信するか、Microsoftにサポートケースを提出します http://connect.Microsoft.com/VisualStudio 。この記事はVC++ 2005向けですが(現在のバージョンのドキュメントが見つかりませんでした)、機能しないシナリオについて説明しています。 http://msdn.Microsoft.com/en-us/library/ms364057(v = vs.80).aspx#nrvo_cpp05_topic

最適化が行われたことを確認したい場合、1つの可能性はアセンブリ出力をチェックすることです。これは、必要に応じてビルドタスクとして自動化できます。

これには、次のように/ 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"
                }
            }
        }
    }

}
3