Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Visual Studio not perform return value optimization (RVO) in this case

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.

like image 914
Chris Drew Avatar asked Sep 21 '14 20:09

Chris Drew


1 Answers

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"                 }             }         }     }  } 
like image 185
Malcolm McCaffery Avatar answered Sep 24 '22 07:09

Malcolm McCaffery