c++ - significado - ¿Por qué Visual Studio no realiza la optimización del valor de retorno(RVO) en este caso?
significado de int main() (1)
Si el código parece que debería optimizarse, pero no se está optimizando, enviaría el error aquí http://connect.microsoft.com/VisualStudio o plantearía un caso de soporte con Microsoft. Este artículo, aunque es para VC ++ 2005 (no pude encontrar una versión actual del documento), explica algunos escenarios en los que no funcionará. http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx#nrvo_cpp05_topic3
Si queremos estar seguros de que la optimización ha ocurrido, una posibilidad es verificar la salida del conjunto. Esto podría automatizarse como una tarea de compilación si así lo desea.
Esto requiere generar una salida de .asm usando la opción / FAs así:
cl test.cpp /FAs
Generará test.asm.
Un ejemplo potencial en PowerShell a continuación, que se puede utilizar de esta manera:
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>
La secuencia de comandos:
# 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"
}
}
}
}
}
Estaba respondiendo una pregunta y recomendando el valor por valor de retorno para un tipo grande porque estaba seguro de que el compilador realizaría la optimización del valor de retorno (RVO) . Pero luego se me indicó que Visual Studio 2013 no estaba realizando RVO en mi código.
He encontrado aquí una pregunta sobre si Visual Studio no puede realizar RVO, pero en ese caso la conclusión parecía ser que, si realmente importa, Visual Studio realizará RVO. En mi caso, sí importa, tiene un impacto significativo en el rendimiento que he confirmado con los resultados de los perfiles. Aquí está el código simplificado:
#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";
}
}
Estoy esperando que el compilador realice RVO en el tipo de getBigFoo()
de getBigFoo()
. Pero parece estar copiando a Foo
lugar.
Soy consciente de que el compilador creará un constructor de copia para Foo
. También soy consciente de que, a diferencia de un compilador compatible con C ++ 11, Visual Studio no crea un constructor de movimiento para Foo
. Pero eso debería estar bien, RVO es un concepto de C ++ 98 y funciona sin semántica de movimiento.
Entonces, la pregunta es, ¿hay una buena razón por la cual Visual Studio 2013 no realiza la optimización del valor de retorno en este caso?
Sé de algunas soluciones. Puedo definir un movimiento-constructor para Foo
:
Foo(Foo&& in) : v(std::move(in.v)) {}
lo cual está bien, pero hay muchos tipos heredados que no tienen constructores de movimiento y sería bueno saber que puedo confiar en RVO con esos tipos. Además, algunos tipos pueden ser copiables inherentemente pero no movibles.
Si cambio de RVO a NVRO (denominado optimización del valor de retorno), entonces Visual Studio parece realizar la optimización:
Foo foo(std::move(v))
return foo;
lo cual es curioso porque pensé que NVRO era menos confiable que RVO.
Aún más curioso es si cambio el constructor de Foo
para que cree y llene el vector
:
Foo(size_t num) : v(num) {
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
}
en lugar de moverlo, cuando intento hacer RVO, funciona:
Foo getBigFoo() {
return Foo(1000000);
}
Me complace ir con una de estas soluciones, pero me gustaría poder predecir cuándo RVO podría fallar así en el futuro, gracias.
Editar: demostración en vivo más concisa de @dyp
Edit2: ¿Por qué no solo escribo return v;
?
Para empezar, no ayuda. Los resultados de Profiler muestran que Visual Studio 2013 todavía copia el vector si solo escribo return v;
E incluso si funcionara, solo sería una solución. No estoy tratando de arreglar realmente esta pieza de código en particular, estoy tratando de entender por qué RVO falla, así que puedo predecir cuándo podría fallar en el futuro. Es cierto que es una forma más concisa de escribir este ejemplo en particular, pero hay muchos casos en los que no podría simplemente escribir return v;
, por ejemplo, si Foo
tiene parámetros de constructor adicionales.