Почему Visual Studio не выполняет оптимизацию возвращаемого значения (RVO) в этом случае

Я отвечал на вопрос и рекомендовал возврат по значению для большого типа, потому что был уверен, что компилятор выполнит оптимизацию возвращаемого значения (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";
  }
}

Я ожидаю, что компилятор выполнит RVO для типа возврата из getBigFoo(), Но, похоже, копирование Foo вместо.

Я знаю, что компилятор создаст конструктор копирования для Foo, Я также знаю, что в отличие от совместимого компилятора C++11 Visual Studio не создает конструктор перемещения для Foo, Но это должно быть хорошо, RVO - это концепция C++98 и работает без семантики перемещения.

Итак, вопрос в том, есть ли веская причина, почему Visual Studio 2013 не выполняет оптимизацию возвращаемого значения в этом случае?

Я знаю несколько обходных путей. Я могу определить Move-конструктор для 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 может потерпеть неудачу, как это в будущем, спасибо.

Изменить: более сжатый живой демо от @dyp

Edit2: почему бы мне просто не написать return v;?

Для начала это не поможет. Результаты Profiler показывают, что Visual Studio 2013 все еще копирует вектор, если я просто пишу return v; И даже если бы это сработало, это был бы только обходной путь. Я не пытаюсь исправить этот конкретный фрагмент кода, я пытаюсь понять, почему RVO дает сбой, поэтому я могу предсказать, когда он может произойти в будущем. Это правда, что это более сжатый способ написания этого конкретного примера, но есть много случаев, когда я не мог просто написать return v;например, если Foo имел дополнительные параметры конструктора.

1 ответ

Если код выглядит так, как будто он должен быть оптимизирован, но не оптимизирован, я отправлю сообщение об ошибке здесь http://connect.microsoft.com/VisualStudio или расскажу о поддержке в Microsoft. Эта статья, хотя она и предназначена для VC++2005 (я не смог найти текущую версию документа), объясняет некоторые сценарии, в которых она не будет работать. http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx

Если мы хотим быть уверены, что оптимизация произошла, можно проверить вывод сборки. При желании это можно автоматизировать как задачу сборки.

Для этого необходимо сгенерировать вывод.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"
                }
            }
        }
    }

}
Другие вопросы по тегам