Foros del Web » Programación para mayores de 30 ;) » C/C++ »

C++11 - Costructores de movimiento - experimento fallido - AVISO: ES LARGO

Estas en el tema de C++11 - Costructores de movimiento - experimento fallido - AVISO: ES LARGO en el foro de C/C++ en Foros del Web. Hola a todos. Para probar las gentilezas del estándar C++11, en concreto las referncias a rvalues y las semántica de movimientos he hecho un pequeño ...
  #1 (permalink)  
Antiguo 08/04/2014, 16:45
 
Fecha de Ingreso: febrero-2014
Mensajes: 55
Antigüedad: 10 años, 1 mes
Puntos: 3
C++11 - Costructores de movimiento - experimento fallido - AVISO: ES LARGO

Hola a todos.
Para probar las gentilezas del estándar C++11, en concreto las referncias a rvalues y las semántica de movimientos he hecho un pequeño experimento.

Primero diré que las herramientas utilizadas son:
- Geany como IDE
- g++ versiones 4.6 y 4.7
- gprof

He hecho 2 programas, uno que es del clásico C++ (compilado con g++ v4.6) y otro que es C++11 con constructores de movimiento (compilado con g++ v4.7 -std=c++0x)

Tras esto he pasado por un profiler ambos programas y lo resultados me resultan tan extraños que no los puedo interpretar. Adjunto los códigos y las salidas del profiler:

Nota: el código es largo, pero es porque son 4 ficheros fuente y 2 cabceras completamente simples, no es nada duro de leer.

Código:
//A.cpp:
#include <cstring>
#include "A.h"

A::A(size_t sz=512):size(sz), buf(new char[size]){};
A::A(size_t sz,char *c):size(sz){ strcpy(this->buf,c); }

A::~A(){ delete[] buf; };

A::A(const A &a){
	this->size=a.size;
	this->buf=new char[size];
	strcpy(this->buf,a.buf);
}

A& A::operator=(const A &a){
	A *aux=new A(a.size,a.buf);
	return *aux;
}

//A.h
#include <cstdlib>

#ifndef A_H
#define A_H 1

class A{
	private:
		size_t size;
		char * buf;
		
		explicit A(size_t ,char *);
		
	public:
		explicit A(size_t );
		~A();
		A(const A&);
		A & operator=(const A&);
};

#endif
//--------------------------------------------------------------
//B.cpp:
#include <cstring>
#include <cstddef> //<====== C++11
#include "B.h"

B::B(size_t sz=512):size(sz), buf(new char[size]){};
B::B(size_t sz,char *c):size(sz){ strcpy(this->buf,c); }

B::B(B &&other):size(0), buf(nullptr){
	//Traemos a 'other' aquí
	this->size=other.size;
	this->buf=other.buf;
	//Reseteamos a 'other' para dejarlo en estado
	//consistente
	other.size=0;
	other.buf=nullptr;
};

B::~B(){ delete[] buf; };

B::B(const B &a){
	this->size=a.size;
	this->buf=new char[size];
	strcpy(this->buf,a.buf);
}

B& B::operator=(const B &a){
	B *aux=new B(a.size,a.buf);
	return *aux;
}

//B.h
#include <cstdlib>

#ifndef B_H
#define B_H 1

class B{
	private:
		size_t size;
		char * buf;
		
		explicit B(size_t ,char *);
		
	public:
		explicit B(size_t );
		B(B &&);
		~B();
		B(const B&);
		B & operator=(const B&);
};

#endif

//--------------------------------------------------------------
//main1.cpp:
#include <iostream>
#include <vector>
#include "A.h"

using namespace std;

int main(){
	vector<A> vb;
	for(int j=0;j<200;j++){
		for(int i=0;i<50000;i++){
			vb.push_back(A(1024));
		}
		for(int i=0;i<50000;i++){
			vb.pop_back();
		}
	}
	cout<<"Terminado"<<endl;
	return 0;
}

//main2.cpp
#include <iostream>
#include <vector>
#include "B.h"

using namespace std;

int main(){
	vector<B> vb;
	for(int j=0;j<200;j++){
		for(int i=0;i<50000;i++){
			vb.push_back(B(1024));
		}
		for(int i=0;i<50000;i++){
			vb.pop_back();
		}
	}
	cout<<"Terminado main2"<<endl;
	return 0;
}
Bien, tras pasar ambos mains por el mismo profiler, en la misma máquina (obvio) obtengo lo siguiente:
Advierto que se ven muy mal las distintas columnas, adjunto un resumen al final con lo importante.

Resultado de main.cpp (sin move semantics, g++ v4.6):
Flat profile:

Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls us/call us/call name
33.33 0.01 0.01 1115535 0.01 0.01 A::A(A const&)
33.33 0.02 0.01 main
16.67 0.03 0.01 1050000 0.00 0.00 std::vector<A, std::allocator<A> >::pop_back()
16.67 0.03 0.01 1050000 0.00 0.01 std::vector<A, std::allocator<A> >::push_back(A const&)
0.00 0.03 0.00 2165535 0.00 0.00 A::~A()
0.00 0.03 0.00 1115535 0.00 0.00 operator new(unsigned int, void*)
0.00 0.03 0.00 1050000 0.00 0.00 A::A(unsigned int)
...
...
...

Resultado de main2.cpp (con move-semantics, g++ v4.7):
Flat profile:

Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
25.00 0.09 0.09 main
13.89 0.14 0.05 20065535 0.00 0.00 B::~B()
12.50 0.18 0.04 10000000 0.00 0.00 std::vector<B, std::allocator<B> >::pop_back()
11.11 0.23 0.04 10065535 0.00 0.00 B::B(B&&)
8.33 0.26 0.03 20065552 0.00 0.00 B&& std::forward<B>(std::remove_reference<B>::type&)
8.33 0.28 0.03 10000000 0.00 0.00 B::B(unsigned int)
5.56 0.30 0.02 10000000 0.00 0.00 void __gnu_cxx::new_allocator<B>::construct<B>(B*, B&&)
2.78 0.32 0.01 10000000 0.00 0.00 __gnu_cxx::new_allocator<B>::destroy(B*)
2.78 0.33 0.01 10000000 0.00 0.00 void std::vector<B, std::allocator<B> >::emplace_back<B>(B&&)
2.78 0.34 0.01 10000000 0.00 0.00 std::vector<B, std::allocator<B> >::push_back(B&&)
2.78 0.34 0.01 65569 0.00 0.00 bool std::operator!=<B*>(std::move_iterator<B*> const&, std::move_iterator<B*> const&)
...
...
...

Profiler del main.cpp (%time, cumulative_seconds, self_seconds, calls, self_us/call, total_us/call )
Constructor de copia:
33.33 | 0.01 | 0.01 | 1115535 | 0.01 | 0.01 A::A(A const&)
Destructor:
0.00 | 0.03 | 0.00 | 2165535 | 0.00 | 0.00

Profiler del main2.cpp (%time, cumulative_seconds, self_seconds, calls, self_us/call, total_us/call )
Constructor de movimiento:
11.11 | 0.23 | 0.04 10065535 | 0.00 | 0.00 B::B(B&&)
Destructor:
13.89 | 0.14 | 0.05 20065535 | 0.00 | 0.00

Bueno, os digo lo que no entiendo:
No sólo que el código con contructor de movimiento es mas lento, esque no entiendo por qué en el main.cpp hay 1kk y pico de llamadas al constructor y en el main2.cpp hay mas de 10kk.

Además en main.cpp hay 1050000 llamadas a push_back() y pop_back() mientras que en main2.cpp hay 10000000 llamadas a push_back() y pop_back(), y esto ya si que no lo entiendo, porque los bucles for son idénticos.

¿Alguien puede ayudarme?

Gracias de antemano.

Última edición por SARGE553413; 08/04/2014 a las 17:01
  #2 (permalink)  
Antiguo 09/04/2014, 06:42
 
Fecha de Ingreso: junio-2008
Ubicación: Seattle, USA
Mensajes: 733
Antigüedad: 15 años, 10 meses
Puntos: 61
Respuesta: C++11 - Costructores de movimiento - experimento fallido - AVISO: ES LARGO

Pon la clase A y B en el mismo programa para compilarlos con el mismo compilador.
Luego cambia tu main() para que invoque a una funcion que ejercite el mismo codigo para los 2 tipos de clases, por ejemplo asi:

Código C++:
Ver original
  1. template <typename T>
  2. int testf(T dummy){
  3.         cout << "Comienzo test " << endl;
  4.         vector<T> vb;
  5.         for(int j=0;j<200;j++){
  6.                 cout << j << "  Insertando 50000 elementos " << endl;
  7.                 for(int i=0;i<50000;i++){
  8.                         vb.push_back(T(1024));
  9.                 }
  10.                 cout << j << "  Eliminando 50000 elementos " << endl;
  11.                 for(int i=0;i<50000;i++){
  12.                         vb.pop_back();
  13.                 }
  14.         }
  15.         cout<<"Terminado"<<endl;
  16.         return 0;
  17. }
  18.  
  19. int main(){
  20.     A a;
  21.     B b;
  22.  
  23.     testf(a);
  24.     testf(b);
  25.  
  26.     return 0;
  27. }

Has profiling de ese programa y verifica que los constructores se esten llamando igual cantidad de veces.

En un asunto paralelo, este constructor
Código C++:
Ver original
  1. B::B(size_t sz,char *c):size(sz){ strcpy(this->buf,c); }

esta copiando dentro de buf y, si se está construyendo, ¿adonde apunta buf?
La clase A tiene un constructor similar.

Hay otros posibles problemas, pero estan mas relacionados con problemas que no se han presentado (aun) y no con los que muestras aqui, por ejemplo, copiar usando strcpy, mejor memcpy o strncpy, etc.
__________________
Visita mi perfil en LinkedIn
  #3 (permalink)  
Antiguo 09/04/2014, 07:40
 
Fecha de Ingreso: febrero-2014
Mensajes: 55
Antigüedad: 10 años, 1 mes
Puntos: 3
Respuesta: C++11 - Costructores de movimiento - experimento fallido - AVISO: ES LARGO

Sí es verdad lo que dices de buf y tal...
Error de nvoato pero es porque me preocupaba sobre todo el "test" que hago, por ej. el operador de asignación de la clase B no lo he implementado con move smenatics, per oes por ir con mucha prisa.

Voy a probar lo que dices y vuelvo a subir datos.

Gracias.
  #4 (permalink)  
Antiguo 10/04/2014, 16:07
 
Fecha de Ingreso: febrero-2014
Mensajes: 55
Antigüedad: 10 años, 1 mes
Puntos: 3
Respuesta: C++11 - Costructores de movimiento - experimento fallido - AVISO: ES LARGO

Hola de nuevo.

He hecho las pruebas que me han aconsejado, y además de usar solo el profiler, he medido el tiempo "a huevo" con la librería ctime.

El resultado es que, habiendo compilado con la opción -O3, la clase con constructor de movimiento es algo más de un 10% más rápida.
Quiero aclarar que solo se ha probado el constructor de movimiento, imagino que si añadimos el operador de asignación de movimiento el porcentaje de mejora se incrementará aún más.

Bueno eso es todo, saludos y gracias.

Etiquetas: experimento, int, largo, movimiento, programa, string
Atención: Estás leyendo un tema que no tiene actividad desde hace más de 6 MESES, te recomendamos abrir un Nuevo tema en lugar de responder al actual.
Respuesta




La zona horaria es GMT -6. Ahora son las 21:45.