¿Puede modern C++ obtener rendimiento de forma gratuita?


A veces se afirma que C++11/14 puede obtener un aumento de rendimiento incluso cuando simplemente compila código de C++98. La justificación es generalmente a lo largo de las líneas de la semántica de movimiento, ya que en algunos casos los constructores rvalue se generan automáticamente o ahora forman parte de la STL. Ahora me pregunto si estos casos fueron previamente ya manejados por RVO o optimizaciones de compiladores similares.

Mi pregunta entonces es si podría darme un ejemplo real de una pieza de código C++98 que, sin modificaciones, se ejecuta más rápido utilizando un compilador compatible con las nuevas características del lenguaje. Entiendo que no se requiere un compilador de conformidad estándar para hacer la elisión de copia y solo por esa razón la semántica de movimiento podría producir velocidad, pero me gustaría ver un caso menos patológico, si se quiere.

EDIT: Solo para ser claro, no estoy preguntando si los nuevos compiladores son más rápidos que los compiladores antiguos, sino más bien si hay código por el cual agregar-std = c++14 a mis banderas de compilador se ejecutaría más rápido (evitar copias, pero si usted puede llegar a cualquier otra cosa además de mover semántica, yo también estaría interesado)

Author: alarge, 2014-12-22

2 answers

Soy consciente de 5 categorías generales donde recompilar un compilador de C++03 como C++11 puede causar aumentos de rendimiento ilimitados que prácticamente no están relacionados con la calidad de la implementación. Todas estas son variaciones de la semántica de movimiento.

std::vector reasignar

struct bar{
  std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03

Cada vez que el búfer de foo es reasignado en C++03 se copia cada vector en bar.

En C++11 en cambio mueve la bar::datas, que es básicamente libre.

En este caso, esto se basa en optimizaciones dentro del contenedor std vector. En todos los casos a continuación, el uso de std contenedores es solo porque son objetos de C++ que tienen una semántica move eficiente en C++11 "automáticamente" cuando actualiza su compilador. Los objetos que no lo bloquean y que contienen un contenedor std también heredan los constructores automáticos mejorados move.

Fallo de la NRVO

Cuando falla NRVO (llamado optimización de valor de retorno), en C++03 vuelve a copiar, en C++11 vuelve a copiar mover. Los fracasos de NRVO son fáciles:

std::vector<int> foo(int count){
  std::vector<int> v; // oops
  if (count<=0) return std::vector<int>();
  v.reserve(count);
  for(int i=0;i<count;++i)
    v.push_back(i);
  return v;
}

O incluso:

std::vector<int> foo(bool which) {
  std::vector<int> a, b;
  // do work, filling a and b, using the other for calculations
  if (which)
    return a;
  else
    return b;
}

Tenemos tres valores the el valor devuelto, y dos valores diferentes dentro de la función. Elision permite que los valores dentro de la función se 'fusionen' con el valor devuelto, pero no entre sí. Ambos no se pueden fusionar con el valor devuelto sin fusionarse entre sí.

El problema básico es que NRVO elision es frágil, y el código con cambios no cerca del sitio return de repente puede tener reducción del rendimiento en ese punto sin emisión de diagnóstico. En la mayoría de los casos de fallo NRVO C++11 termina con un move, mientras que C++03 termina con una copia.

Devolviendo un argumento de función

La elisión también es imposible aquí:

std::set<int> func(std::set<int> in){
  return in;
}

En C++11 esto es barato: en C++03 no hay forma de evitar la copia. Los argumentos a las funciones no pueden ser elididos con el valor devuelto, porque el tiempo de vida y la ubicación del parámetro y el valor devuelto son administrados por la llamada codificar.

Sin embargo, C++11 puede moverse de uno a otro. (En un ejemplo de menos juguete, algo se podría hacer a la set).

push_back o insert

Finalmente la elisión en contenedores no ocurre: pero C++11 sobrecarga los operadores de inserción de movimiento de rvalue, lo que guarda copias.

struct whatever {
  std::string data;
  int count;
  whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );

En C++03 se crea un whatever temporal, luego se copia en el vector v. 2 std::string se asignan búferes, cada uno con datos idénticos, y uno se descarta.

En C++11 se crea un whatever temporal. Las whatever&& push_back sobrecarga entonces move es que temporal en el vector v. Se asigna un búfer std::string y se mueve al vector. Se descarta un std::string vacío.

Asignación

Robado de la respuesta de @Jarod42 a continuación.

La elisión no puede ocurrir con la asignación, sino move-from can.

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

Aquí some_function devuelve un candidato a elide de, pero debido a que no se utiliza para construir un objeto directamente, no puede ser elided. En C++03, lo anterior resulta en que el contenido del temporal se copie en some_value. En C++11, se mueve a some_value, que básicamente es libre.


Para el efecto completo de lo anterior, necesita un compilador que sintetice constructores de movimiento y asignación para usted.

MSVC 2013 implementa constructores de movimiento en contenedores std, pero no sintetiza constructores de movimiento en sus tipos.

Así que los tipos que contienen std::vector s y similares no obtienen tales mejoras en MSVC2013, pero comenzarán a conseguirlas en MSVC2015.

Clang y gcc han implementado constructores de movimiento implícito desde hace mucho tiempo. El compilador 2013 de Intel soportará la generación implícita de constructores de movimiento si pasa -Qoption,cpp,--gen_move_operations (no lo hacen por defecto en un esfuerzo por ser cross-compatible con MSVC2013).

 215
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-17 15:40:16

Si tienes algo como:

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

Tienes una copia en C++03, mientras que tienes una asignación de movimiento en C++11. así que tienes optimización gratuita en ese caso.

 44
Author: Jarod42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-22 04:50:36