compilador - (Falta) mejoras de rendimiento con C++ 11 mover semántica
gcc c++ (3)
Esto definitivamente debería ser más rápido cuando no necesita copiar objetos temporales cada vez que se intercambian dos elementos.
std::string
tiene un miembro de swap
, por lo tanto, la sort
ya usará eso, y su implementación interna ya se moverá a la semántica, efectivamente. Y no verá una diferencia entre copiar y mover para std::string
mientras esté involucrado el SSO. Además, algunas versiones de GCC todavía tienen una implementación basada en COW no permitida en C ++ 11, que tampoco vería mucha diferencia entre copiar y mover.
He estado escribiendo código C ++ 11 desde hace bastante tiempo, y no he hecho ninguna evaluación comparativa de él, solo esperaba que cosas como las operaciones vectoriales "solo fueran más rápidas" ahora con semántica de movimientos. Entonces, cuando en realidad se realizan pruebas comparativas con GCC 4.7.2 y clang 3.0 (compiladores predeterminados en Ubuntu 12.10 de 64 bits) obtengo resultados muy insatisfactorios. Este es mi código de prueba:
EDITAR: Con respecto a las (buenas) respuestas publicadas por @DeadMG y @ronag, cambié el tipo de elemento de std::string
a my::string
que no tiene un swap()
, e hice todas las cadenas internas más grandes (200 -700 bytes) para que no sean víctimas de SSO.
EDIT2: VAC fue la razón. Se adaptó de nuevo el código por los excelentes comentarios, se cambió el almacenamiento de std::string
a std::vector<char>
y se omiten los onstructors de copiar / mover (dejando que el compilador los genere en su lugar). Sin COW, la diferencia de velocidad es realmente enorme.
EDIT3: se volvió a agregar la solución anterior cuando se compiló con -DCOW
. Esto hace que el almacenamiento interno sea std::string
lugar de std::vector<char>
como lo solicita @chico.
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>
static std::size_t dec = 0;
namespace my { class string
{
public:
string( ) { }
#ifdef COW
string( const std::string& ref ) : str( ref ), val( dec % 2 ? - ++dec : ++dec ) {
#else
string( const std::string& ref ) : val( dec % 2 ? - ++dec : ++dec ) {
str.resize( ref.size( ) );
std::copy( ref.begin( ), ref.end( ), str.begin( ) );
#endif
}
bool operator<( const string& other ) const { return val < other.val; }
private:
#ifdef COW
std::string str;
#else
std::vector< char > str;
#endif
std::size_t val;
}; }
template< typename T >
void dup_vector( T& vec )
{
T v = vec;
for ( typename T::iterator i = v.begin( ); i != v.end( ); ++i )
#ifdef CPP11
vec.push_back( std::move( *i ) );
#else
vec.push_back( *i );
#endif
}
int main( )
{
std::ifstream file;
file.open( "/etc/passwd" );
std::vector< my::string > lines;
while ( ! file.eof( ) )
{
std::string s;
std::getline( file, s );
lines.push_back( s + s + s + s + s + s + s + s + s );
}
while ( lines.size( ) < ( 1000 * 1000 ) )
dup_vector( lines );
std::cout << lines.size( ) << " elements" << std::endl;
std::sort( lines.begin( ), lines.end( ) );
return 0;
}
Lo que esto hace es leer / etc / passwd en un vector de líneas, luego duplicar este vector en sí mismo una y otra vez hasta que tengamos al menos 1 millón de entradas. Aquí es donde la primera optimización debería ser útil, no solo el std::move()
explícito que se ve en dup_vector()
, sino también el push_back
per se debería mejorar cuando se necesita cambiar el tamaño (crear una nueva copia) la matriz interna .
Finalmente, se ordena el vector. Esto definitivamente debería ser más rápido cuando no necesita copiar objetos temporales cada vez que se intercambian dos elementos.
Compilo y ejecuto estas dos formas, una como C ++ 98, la siguiente como C ++ 11 (con -DCPP11 para el movimiento explícito):
1> $ rm -f a.out ; g++ --std=c++98 test.cpp ; time ./a.out
2> $ rm -f a.out ; g++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
3> $ rm -f a.out ; clang++ --std=c++98 test.cpp ; time ./a.out
4> $ rm -f a.out ; clang++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
Con los siguientes resultados (dos veces por cada compilación):
GCC C++98
1> real 0m9.626s
1> real 0m9.709s
GCC C++11
2> real 0m10.163s
2> real 0m10.130s
Por lo tanto, es un poco más lento de ejecutar cuando se compila como código C ++ 11. Resultados similares son para el clang:
clang C++98
3> real 0m8.906s
3> real 0m8.750s
clang C++11
4> real 0m8.858s
4> real 0m9.053s
¿Alguien puede decirme por qué esto es? ¿Están los compiladores optimizando tan bien incluso cuando compilan para pre-C ++ 11, que prácticamente llegan a mover el comportamiento semántico después de todo? Si agrego -O2
, todo el código se ejecuta más rápido, pero los resultados entre los diferentes estándares son casi los mismos que los anteriores.
EDITAR : Nuevos resultados con my :: string y en lugar de std :: string, y cadenas individuales más grandes:
$ rm -f a.out ; g++ --std=c++98 test.cpp ; time ./a.out
real 0m16.637s
$ rm -f a.out ; g++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real 0m17.169s
$ rm -f a.out ; clang++ --std=c++98 test.cpp ; time ./a.out
real 0m16.222s
$ rm -f a.out ; clang++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real 0m15.652s
Hay diferencias muy pequeñas entre C ++ 98 y C + 11 con semántica de movimiento. Un poco más lento con C ++ 11 con GCC y un poco más rápido con el clang, pero con diferencias muy pequeñas.
EDIT2: Ahora sin std::string
''s COW, la mejora de rendimiento es enorme:
$ rm -f a.out ; g++ --std=c++98 test.cpp ; time ./a.out
real 0m10.313s
$ rm -f a.out ; g++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real 0m5.267s
$ rm -f a.out ; clang++ --std=c++98 test.cpp ; time ./a.out
real 0m10.218s
$ rm -f a.out ; clang++ --std=c++11 -DCPP11 test.cpp ; time ./a.out
real 0m3.376s
Con la optimización, la diferencia es mucho más grande también:
$ rm -f a.out ; g++ -O2 --std=c++98 test.cpp ; time ./a.out
real 0m5.243s
$ rm -f a.out ; g++ -O2 --std=c++11 -DCPP11 test.cpp ; time ./a.out
real 0m0.803s
$ rm -f a.out ; clang++ -O2 --std=c++98 test.cpp ; time ./a.out
real 0m5.248s
$ rm -f a.out ; clang++ -O2 --std=c++11 -DCPP11 test.cpp ; time ./a.out
real 0m0.785s
Por encima de mostrar un factor de ~ 6-7 veces más rápido con C ++ 11.
Gracias por los grandes comentarios y respuestas. Espero que esta publicación sea útil e interesante para otros también.
Creo que tendrás que perfilar el programa. Tal vez la mayor parte del tiempo se gasta en las líneas T v = vec;
y el std::sort(..)
de un vector de 20 millones de cuerdas !!! Nada que ver con la semántica del movimiento.
Esto se debe probablemente a la pequeña optimización de cadenas , que puede ocurrir (dependiendo del compilador) para cadenas más cortas que, por ejemplo, 16 caracteres. Supongo que todas las líneas en el archivo son bastante cortas, ya que son contraseñas.
Cuando la optimización de cadena pequeña está activa para una cadena en particular, el movimiento se realiza como una copia.
Necesitará cadenas más grandes para ver las mejoras de velocidad con la semántica de movimiento.