Подтвердить что ты не робот

Почему 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. Я также знаю, что в отличие от совместимого компилятора С++ 11 Visual Studio не создает конструктор move для Foo. Но это должно быть ОК, RVO является концепцией С++ 98 и работает без семантики перемещения.

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

Я знаю несколько обходных решений. Я могу определить конструктор move для Foo:

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

что хорошо, но есть много устаревших типов, у которых нет конструкторов move, и было бы неплохо узнать, что я могу полагаться на RVO с этими типами. Кроме того, некоторые типы могут быть неотъемлемо скопируемыми, но не подвижными.

Если я перехожу из RVO в NVRO (с именем return value optimization), то 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;?

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

4b9b3361

Ответ 1

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

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

Для этого требуется генерация вывода .asm с использованием опции /FAs:

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> 

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

}