performance - ¿Puede el moderno C++obtener rendimiento de forma gratuita?




c++11 move-semantics c++14 (3)

si tienes algo como:

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

// some code

v = foo();

Obtuvo una copia en C ++ 03, mientras que recibió una asignación de movimiento en C ++ 11. Así que tienes optimización libre en ese caso.

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 en la línea de semántica de movimientos, ya que en algunos casos los constructores de valores se generan automáticamente o ahora forman parte de la STL. Ahora me pregunto si estos casos ya fueron manejados por RVO o por optimizaciones de compiladores similares.

Entonces, mi pregunta es si podría darme un ejemplo real de un fragmento de código de C ++ 98 que, sin modificaciones, se ejecute más rápido utilizando un compilador que admita las nuevas características de idioma. Entiendo que no se requiere que un compilador que cumpla con los estándares para hacer la elision de la copia y solo por esa razón, mover la semántica podría generar velocidad, pero me gustaría ver un caso menos patológico, por así decirlo.

EDITAR: Para ser claro, no estoy preguntando si los compiladores nuevos son más rápidos que los compiladores antiguos, sino si existe un código mediante el cual agregar -std = c ++ 14 a las banderas de mi compilador se ejecutaría más rápido (evite las copias, pero si puedo llegar a cualquier otra cosa además de mover semántica, también me interesaría)


Soy consciente de 5 categorías generales donde la compilación de un compilador 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. Estas son todas las variaciones de la semántica del movimiento.

std::vector reasignate

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 se reasigna en C ++ 03, se copian todos los vector en la bar .

En C ++ 11, en cambio, mueve la bar::data s, que es básicamente gratuita.

En este caso, esto se basa en optimizaciones dentro del vector contenedor std . En todos los casos a continuación, el uso de contenedores estándar es solo porque son objetos C ++ que tienen una semántica de 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 de move mejorados automáticos.

Fallo de NRVO

Cuando NRVO (llamado optimización de valor de retorno) falla, en C ++ 03 se vuelve a copiar, en C ++ 11 se vuelve a mover. Los fallos 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: el valor de retorno y dos valores diferentes dentro de la función. Elision permite que los valores dentro de la función se 'fusionen' con el valor de retorno, pero no entre ellos. Ambos no pueden fusionarse con el valor de retorno sin fusionarse entre sí.

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

Devolviendo un argumento de función

Elision 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 de las funciones no pueden eliminarse con el valor de retorno, ya que la vida útil y la ubicación del parámetro y el valor de retorno son administradas por el código que llama.

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

push_back o insert

Finalmente, no se produce la elección en contenedores: pero C ++ 11 sobrecarga rvalue mover operadores de inserción, 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 temporal, luego se copia en el vector v . Se asignan 2 std::string buffers, cada uno con datos idénticos, y uno se descarta.

En C ++ 11 se crea un temporal. Lo que whatever&& push_back overload luego move s que temporal al vector v . Se asigna un std::string buffer y se mueve al vector. Se descarta un std::string vacío.

Asignación

Robado de la respuesta de @Jarod42 debajo.

Elision no puede ocurrir con la asignación, pero la mudanza puede.

std::set<int> some_function();

std::set<int> some_value;

// code

some_value = some_function();

aquí some_function devuelve un candidato para eludir, pero como no se usa para construir un objeto directamente, no se puede eliminar. En C ++ 03, lo anterior da como resultado que los contenidos del temporal se copien en some_value . En C ++ 11, se mueve a some_value , que básicamente es gratuito.

Para lograr el efecto completo de lo anterior, necesita un compilador que sintetice constructores de movimientos y asignaciones para usted.

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

Por lo tanto, los tipos que contienen std::vector s y similares no obtienen tales mejoras en MSVC2013, pero comenzarán a obtenerlas en MSVC2015.

clang y gcc han implementado desde hace tiempo implícitos constructores de movimiento. El compilador de Intel 2013 admitirá la generación implícita de constructores de movimientos si pasa -Qoption,cpp,--gen_move_operations (no lo hacen de manera predeterminada para intentar ser compatibles con MSVC2013).


Utilice Valgrind, callgrind y kcachegrind:

valgrind --tool=callgrind ./(Your binary)

Genera callgrind.out.x. Léelo usando kcachegrind.

Utilice gprof (add -pg):

cc -o myprog myprog.c utils.c -g -pg 

(No es tan bueno para múltiples hilos, punteros de función)

Utilice google-perftools:

Utiliza muestreo de tiempo, E / S y cuellos de botella de la CPU se revelan.

Intel VTune es el mejor (gratis para fines educativos).

Otros: AMD Codeanalyst (desde que se reemplazó con AMD CodeXL), OProfile, herramientas 'perf' (apt-get install linux-tools)





c++ performance c++11 move-semantics c++14