Ver Mensaje Individual
  #1 (permalink)  
Antiguo 08/04/2014, 16:45
SARGE553413
 
Fecha de Ingreso: febrero-2014
Mensajes: 55
Antigüedad: 10 años, 2 meses
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