compiler compilador c++ gcc c++11 clang move-semantics

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.