View Full Version : [C++ / std:sort()]
Teo@Unix
26-06-2013, 11:54
Buondì
La funzione sort() durante il processo di ordinamento, mi modifica gli elementi. In particolare, svuota dei vettori.
Il mio intento è ordinare un vector<> di oggetti di tipo:
template<typename T>
class geneBase
{
...
bool operator<(geneBase b) {return(fitness < b.GetFitness());}
...
vector<T> *code;
};
Dove ho definito l'operatore < utilizzato da sort().
In un'altra classe definisco il vettore di questi elementi e utilizzo sort():
template <typename T>
class populationBase
{
...
vector<geneBase<T>> population;
...
};
Quindi nel file .cpp:
std::sort(population.begin(), population.end());
Tutti i vettori vector<T> negli elementi geneBase<T> poi risultano vuoti...
non devo usare un puntatore nella dichiarazione?
grazie dell'aiuto.
vendettaaaaa
26-06-2013, 14:17
Buondì
La funzione sort() durante il processo di ordinamento, mi modifica gli elementi. In particolare, svuota dei vettori.
Il mio intento è ordinare un vector<> di oggetti di tipo:
template<typename T>
class geneBase
{
...
bool operator<(geneBase b) {return(fitness < b.GetFitness());}
...
vector<T> *code;
};
Dove ho definito l'operatore < utilizzato da sort().
In un'altra classe definisco il vettore di questi elementi e utilizzo sort():
template <typename T>
class populationBase
{
...
vector<geneBase<T>> population;
...
};
Quindi nel file .cpp:
std::sort(population.begin(), population.end());
Tutti i vettori vector<T> negli elementi geneBase<T> poi risultano vuoti...
non devo usare un puntatore nella dichiarazione?
grazie dell'aiuto.
Prima di tutto: i nomi delle classi dovrebbero cominciare con una maiuscola. Non è obbligatorio, ma tutti scrivono così, quindi mi conformerei.
Hai definito un copy constructor e un operator=(const geneBase&) per la tua classe? Se sì, posta il codice, il problema è lì.
Perchè std::sort usa std::swap, che è definita così:
template<class T> void swap(T& a, T& b)
{
T temp = a; // Inizializzo temp copiando a, usando il costruttore geneBase(const geneBase&)
a = b; // Assegno b ad a con operator=(const geneBase&)
b = temp; // Assegno temp a b con operator=(const geneBase&)
}
Quindi, nell'inversione di posto di due elementi entra in gioco come gli oggetti della tua classe vengono copiati e/o copy-constructed. A seconda che il puntatore code sia usato come indirizzo o come array, dovrai comportarti diversamente. Ma con ogni probabilità devi gestirlo tu, perchè il copy constructor sintetizzato dal compilatore attualmente non fa quello che intendi tu.
Teo@Unix
26-06-2013, 14:38
Ciao, grazie della risposta.
Si, allora intanto ho risolto evitando l'uso del puntatore.
ma dammi cmq un parere, ora l'operatore = è definito in questo modo:
template <typename T>
geneBase<T>& geneBase<T>::operator=(const geneBase<T>& obj)
{
if(&obj != this)
{
// Copy elements
this->code.clear();
this->code = obj.code;
}
return *this;
}
sfrutto l'operatore = di vector che mi copia tutto l'array.
vendettaaaaa
26-06-2013, 15:17
Ciao, grazie della risposta.
Si, allora intanto ho risolto evitando l'uso del puntatore.
ma dammi cmq un parere, ora l'operatore = è definito in questo modo:
template <typename T>
geneBase<T>& geneBase<T>::operator=(const geneBase<T>& obj)
{
if(&obj != this)
{
// Copy elements
this->code.clear();
this->code = obj.code;
}
return *this;
}
sfrutto l'operatore = di vector che mi copia tutto l'array.
Eviterei il clear, è ridondante: sia che il vettore da copiare è più grande o più piccolo, il vettore attuale viene distrutto. Se invece le dimensioni combaciano, gli elementi vengono copiati. Tra l'altro leggo ora qui (http://www.cplusplus.com/reference/vector/vector/clear/) che non è garantito che la memoria sia deallocata, quindi è inutile.
Cmq copiare interi vettori può essere pesante, cosa ci devi fare?
Teo@Unix
26-06-2013, 21:52
sono algoritmi genetici... risolvono problemi di ottimizzazione combinatoria.
allora elimino il clear()!
In questo caso ordino l'array, si in effetti ci sono molti casi in cui devo copiare vettori, e poi ogni gene è una sequenza di valori quindi sostanzialmente il tutto è un vector di vector:
vector<vector<bool>> per esempio
quindi come dici, a questo punto meglio usare un puntatore dentro la classe, come avevo fatto all'inizio e modificare gli operatori vari... no?
vendettaaaaa
27-06-2013, 15:39
sono algoritmi genetici... risolvono problemi di ottimizzazione combinatoria.
allora elimino il clear()!
In questo caso ordino l'array, si in effetti ci sono molti casi in cui devo copiare vettori, e poi ogni gene è una sequenza di valori quindi sostanzialmente il tutto è un vector di vector:
vector<vector<bool>> per esempio
quindi come dici, a questo punto meglio usare un puntatore dentro la classe, come avevo fatto all'inizio e modificare gli operatori vari... no?
Se stai usando un compilatore che supporta C++11, definisci un move constructor per la tua classe:
geneBase(geneBase&& other) : code(std::move(other.code)) { // Altro }
In questo modo, std::sort usa std::swap che nella versione C++11 sposta i vettori anzichè copiarli. Per ulteriori info, http://www.stroustrup.com/C++11FAQ.html#rval
Altrimenti bisogna metter mano ai puntatori e non è il massimo!
Teo@Unix
27-06-2013, 15:52
si C++ 11, grazie approfondisco.
Teo@Unix
05-07-2013, 22:52
Ho applicato il tuo consiglio,
inoltre in tutte le altre operazioni di copa sto usando la nuova std::move().
La velocità è aumentata.
Una cosa che mi spiego ancora poco...
un programma in C# similare che avevo fatto in passato è molto ma molto più veloce... mi domando come può essere..
c'è una differenza sostanziale nel come vengono copiati gli array??
se non erro una list in C# è simile un stl vector in c++.
vendettaaaaa
05-07-2013, 23:51
Ho applicato il tuo consiglio,
inoltre in tutte le altre operazioni di copa sto usando la nuova std::move().
La velocità è aumentata.
Una cosa che mi spiego ancora poco...
un programma in C# similare che avevo fatto in passato è molto ma molto più veloce... mi domando come può essere..
c'è una differenza sostanziale nel come vengono copiati gli array??
se non erro una list in C# è simile un stl vector in c++.
Mah, posta il codice se puoi, son curioso! Anch'io in passato avevo fatto una specie di confronto tra C++ e C#...
tomminno
06-07-2013, 11:33
Ho applicato il tuo consiglio,
inoltre in tutte le altre operazioni di copa sto usando la nuova std::move().
La velocità è aumentata.
Una cosa che mi spiego ancora poco...
un programma in C# similare che avevo fatto in passato è molto ma molto più veloce... mi domando come può essere..
c'è una differenza sostanziale nel come vengono copiati gli array??
se non erro una list in C# è simile un stl vector in c++.
Occhio che con C# difficilmente copi il contenuto (anzi copiare un oggetto in C# è proprio un incubo, alla fine sei costretto a serializzare e deserializzare un oggetto per clonarlo), più facile che tu abbia semplicemente copiato il riferimento.
Una condizione più simile al C# la ottieni usando gli smart pointer unique_ptr o shared_ptr.
Attenzione all'abuso della move semantic che è pensata per eliminare la doppia copia di oggetti temporanei e realizzare il perfect forwarding.
Spostare un oggetto lascia l'originale in uno stato consistente ma indefinito...
Gli array nella CLR funziona come gli array in C/C++ solamente se contengono value type (ricordo che String nonostante è classificato come value type, nella realtà è un referecence type)
http://www.codeproject.com/Articles/20481/NET-Type-Internals-From-a-Microsoft-CLR-Perspecti#20
In tutti gli altri casi, non è altro che un array di puntatori
Attenzione all'abuso della move semantic che è pensata per eliminare la doppia copia di oggetti temporanei e realizzare il perfect forwarding.
Spostare un oggetto lascia l'originale in uno stato consistente ma indefinito...
No, è pensato anche per std::swap
/**
* @brief Swaps two values.
* @param __a A thing of arbitrary type.
* @param __b Another thing of arbitrary type.
* @return Nothing.
*/
template<typename _Tp>
inline void
swap(_Tp& __a, _Tp& __b)
#if __cplusplus >= 201103L
noexcept(__and_<is_nothrow_move_constructible<_Tp>,
is_nothrow_move_assignable<_Tp>>::value)
#endif
{
// concept requirements
__glibcxx_function_requires(_SGIAssignableConcept<_Tp>)
_Tp __tmp = _GLIBCXX_MOVE(__a);
__a = _GLIBCXX_MOVE(__b);
__b = _GLIBCXX_MOVE(__tmp);
}
tomminno
07-07-2013, 12:56
Gli array nella CLR funziona come gli array in C/C++ solamente se contengono value type (ricordo che String nonostante è classificato come value type, nella realtà è un referecence type)
String è un reference type immutabile non è un value type. In più gli operatori di uguaglianza comparano il contenuto piuttosto che il riferimento. Ma niente di string lo categorizza come value type.
In tutti gli altri casi, non è altro che un array di puntatori
La differenza tra i 2 linguaggi si vede meglio paragonando List<T> a std::vector<T>, piuttosto che con gli array
No, è pensato anche per std::swap
/**
* @brief Swaps two values.
* @param __a A thing of arbitrary type.
* @param __b Another thing of arbitrary type.
* @return Nothing.
*/
template<typename _Tp>
inline void
swap(_Tp& __a, _Tp& __b)
#if __cplusplus >= 201103L
noexcept(__and_<is_nothrow_move_constructible<_Tp>,
is_nothrow_move_assignable<_Tp>>::value)
#endif
{
// concept requirements
__glibcxx_function_requires(_SGIAssignableConcept<_Tp>)
_Tp __tmp = _GLIBCXX_MOVE(__a);
__a = _GLIBCXX_MOVE(__b);
__b = _GLIBCXX_MOVE(__tmp);
}
Infatti con la move semantic eviti le copie di oggetti anche durante lo swap :D
Lo swap è un caso applicativo in cui torna utile evitare le copie di troppo :D
String è un reference type immutabile non è un value type. In più gli operatori di uguaglianza comparano il contenuto piuttosto che il riferimento. Ma niente di string lo categorizza come value type.
Sì è così, non so perchè ero convinto che MS lo aveva inserito nella lista dei value type
Ho una pessima memoria
La differenza tra i 2 linguaggi si vede meglio paragonando List<T> a std::vector<T>, piuttosto che con gli array
Non vedo il perchè, dato che entrambi lavorano sugli array - e quindi le differenze che si notano sono appunto conseguenza di cos'è un array per la CLR rispetto C/C++ e non come List internamente gestisce gli array
Infatti con la move semantic eviti le copie di oggetti anche durante lo swap
Lo swap è un caso applicativo in cui torna utile evitare le copie di troppo
Risposta a:
Attenzione all'abuso della move semantic
Quando è evidente che è la soluzione corretta per questo caso
tomminno
07-07-2013, 14:59
Sì è così, non so perchè ero convinto che MS lo aveva inserito nella lista dei value type
Ho una pessima memoria
Non vedo il perchè, dato che entrambi lavorano sugli array - e quindi le differenze che si notano sono appunto conseguenza di cos'è un array per la CLR rispetto C/C++ e non come List internamente gestisce gli array
Semplicemente perchè std::vector è una classe :)
Quando in C# copi una lista o un array ne copi il riferimento, quando in C++ copi un vector esegui una deep copy, mentre con gli array essendo fondamentalmente dei puntatori questo non succede.
Risposta a:
Quando è evidente che è la soluzione corretta per questo caso
Ovviamente l'abuso che se ne può fare è da parte del programmatore, gli algortimi della libreria standard come swap generalmente non soffrono di questi problemi :D
Semplicemente perchè std::vector è una classe
Quando in C# copi una lista o un array ne copi il riferimento, quando in C++ copi un vector esegui una deep copy, mentre con gli array essendo fondamentalmente dei puntatori questo non succede.
Ah ecco, ora ti seguo maggiormente
Ma forse stiamo sbagliando entrambi
L'errore viene dal fatto che in C++ qualsiasi cosa può essere sia un value type o un reference type a seconda di come viene usato
geneBase contiene un vector - quindi hai ragione tu? No
geneBase contiene non un vector come di dici tu, ma un puntatore ad un vector
Quindi perchè non si comporta come in C#? Perchè forse è stato affrontato il problema dal punto di vista sbagliato, come con l'operatore = che non permette di sfruttare questa situazione
La soluzione migliore è quella di creare una specializzazione per std::swap che semplicemente scambi i due puntatori
Meno overhead rispetto a move, e si sfrutta il fatto che quel vector non è un "value type" ma un "reference type"
vendettaaaaa
07-07-2013, 17:40
La soluzione migliore è quella di creare una specializzazione per std::swap che semplicemente scambi i due puntatori
Meno overhead rispetto a move, e si sfrutta il fatto che quel vector non è un "value type" ma un "reference type"
Probabilmente è la soluzione più veloce, ma io eviterei il puntatore nudo e mi affiderei a std::move, sfuttando il move assignment del vector; dovrebbe esserci un overhead trascurabile. L'op che dice? :D
Teo@Unix
08-07-2013, 10:39
Data la differenza (notevole) di velocità, credo che il punto sia proprio che nel programma C++ CLR che ho scritto si fa una copia totale tutte le volte, quindi nel complesso rallenta molto.
Mentre in C# queste cose essendo già gestite il problema non mi è emerso...
Dovrei provare a lavorare con i puntatori, che di norma ok non si fa, ma in questo caso dove si lavora ad intensità di dati, ovvero alla fine è un continuo modificare array, sia lacosa migliore.
Ora ho anche un problemino di memoria, ovvero da qualche parte non sto rilasciando correttamente, dal debug noto che gli algoritmi di ordinamento fanno qualche pasticcio, prob. il mio operatore di copia non va bene.
Per quanto riguarda il confronto fra C++ e C# trovai un bell'articolo qui:
http://www.codeproject.com/Articles/212856/Head-to-head-benchmark-Csharp-vs-NET
Teo@Unix
08-07-2013, 10:44
Vi faccio vedere a pezzi, perchè il sorgente è un pò lungo...
Queste sono le due classi:
#include "libsann.h"
namespace ga
{
#pragma region Definitions
template<typename T>
class geneBase
{
public:
geneBase(void);
geneBase(uint len, double mrate = 0);
geneBase(geneBase&& other);
~geneBase(void);
// set, get and edit code
vector<T>* GetCode(void);
void SetCode(vector<T> *_code);
// Operators
bool operator<(geneBase b) { return(fitness < b.GetFitness()); }
geneBase& operator=(const geneBase& obj);
geneBase& operator+(const geneBase& obj);
geneBase& operator-(const geneBase& obj);
geneBase& operator+=(const geneBase& obj);
geneBase& operator-=(const geneBase& obj);
geneBase<bool>& operator+(geneBase<bool>& obj);
geneBase<bool>& operator-(geneBase<bool>& obj);
geneBase<bool>& operator+=(geneBase<bool>& obj);
geneBase<bool>& operator-=(geneBase<bool>& obj);
// Mutation
virtual void mutation(void);
// Access methods
inline double GetMutationRate(void) { return mate_rate; }
inline void SetMutationRate(double value) { mutation_rate = value; }
inline double GetFitness(void) { return fitness; }
inline void SetFitness(double value) { fitness = value; }
// Properties
inline uint lenght(void) { return code.size(); }
// Hamming distance
static double HammingDistance(geneBase<T>& a, geneBase<T>& b);
// Crossover
static geneBase& crossover(
geneBase<T>& a, geneBase<T>& b, crossover_mode m = crossover_mode::HAMMING, double mH = 0, uint pop_size = 0
);
protected:
// The code of gene
vector<T> code;
// The fitness value
double fitness;
// Mutation rate
double mutation_rate;
};
template <typename T>
class populationBase
{
public:
populationBase(double mrate = 0.05, double elitism_r = 0.1);
populationBase(uint size, uint len, double mrate = 0.05, double elitism_r = 0.1); // Auto-initialize population
populationBase(vector<geneBase<T>> _pop, double mrate = 0.05, double elitism_r = 0.1);
populationBase(populationBase&& obj);
~populationBase(void);
// Operators
populationBase operator=(populationBase& obj);
populationBase operator+(populationBase& obj);
void join(const populationBase& obj);
// Mate method
void mate(crossover_mode mode = crossover_mode::HAMMING);
// Genetic drift
void BlockgeneticDrift(void);
// Properties
inline double GetDiversity(void) { return diversity; }
inline double GetMaxHammingDistance(void) { return MaxHammingDistance; }
inline uint Size(void) { return population.size(); }
geneBase<T> BestGene(void);
// Access methods
vector<geneBase<T>> *PopulationPtr(void);
protected:
// Population
vector<geneBase<T>> population;
// Maximum Hamming distance
double MaxHammingDistance;
// Elitism rate
double elitism_rate;
// Default mutation rate
double mutation_rate;
uint RunRouletteWheel(utility::MyRandom random, double total_fit);
};
#pragma endregion
Costruttori ed operatori, dove credo siano i problemi:
#pragma region Gene implementation
#pragma region Constructors / Destructors
template <typename T>
geneBase<T>::geneBase(void)
{
fitness = 0;
mutation_rate = 0;
}
template <typename T>
geneBase<T>::geneBase(geneBase&& other)
{
code.assign(other.code.begin(), other.code.end());
fitness = other.fitness;
mutation_rate = other.mutation_rate;
}
geneBase<double>::geneBase(uint len, double mrate)
{
for(uint i = 0; i < len; i++)
{
code.push_back(((double)rand() / (double)RAND_MAX));
}
fitness = 0;
mutation_rate = mrate;
}
geneBase<uint>::geneBase(uint len, double mrate)
{
for(uint i = 0; i < len; i++)
{
code.push_back((uint)rand());
}
fitness = 0;
mutation_rate = mrate;
}
geneBase<bool>::geneBase(uint len, double mrate)
{
for(uint i = 0; i < len; i++)
{
code.push_back((rand() < (RAND_MAX/2)) ? true : false);
}
fitness = 0;
mutation_rate = mrate;
}
geneBase<char>::geneBase(uint len, double mrate)
{
for(uint i = 0; i < len; i++)
{
code.push_back((char)((rand() % 88)+32));
}
fitness = 0;
mutation_rate = mrate;
}
template <typename T>
geneBase<T>::~geneBase(void)
{
code.erase(code.begin(),code.end());
}
#pragma endregion
#pragma region Operators
template <typename T>
geneBase<T>& geneBase<T>::operator=(const geneBase<T>& obj)
{
if(&obj != this)
{
// Copy elements
this->code.assign(obj.code.begin(),obj.code.end());
}
this->fitness = obj.fitness;
this->mutation_rate = obj.mutation_rate;
return *this;
}
template <typename T>
geneBase<T>& geneBase<T>::operator+(const geneBase<T>& obj)
{
_ASSERT(code.size() == obj.GetCode().size());
vector<T> *_code_ = new vector<T>();
for(uint i = 0; i < code.size(); i++)
{
_code_->push_back(code.at(i) + obj.GetCode()->at(i));
}
geneBase<T> *C = new geneBase<T>();
C->SetCode(_code);
return *C;
}
template <typename T>
geneBase<T>& geneBase<T>::operator-(const geneBase<T>& obj)
{
_ASSERT(code.size() == obj.GetCode().size());
vector<T> *_code_ = new vector<T>();
for(uint i = 0; i < code.size(); i++)
{
_code_.push_back(code.at(i) - obj.GetCode()->at(i));
}
geneBase<T> *C = new geneBase<T>();
C->SetCode(_code);
return *C;
}
template <typename T>
geneBase<T>& geneBase<T>::operator+=(const geneBase<T>& obj)
{
_ASSERT(code.size() == obj.GetCode().size());
for(uint i = 0; i < code.size(); i++)
{
code->at(i) += obj.GetCode()->at(i);
}
this->fitness = 0;
return *this;
}
template <typename T>
geneBase<T>& geneBase<T>::operator-=(const geneBase<T>& obj)
{
_ASSERT(code.size() == obj.GetCode().size());
for(uint i = 0; i < code.size(); i++)
{
code.at(i) -= obj.GetCode()->at(i);
}
this->fitness = 0;
return *this;
}
geneBase<bool>& geneBase<bool>::operator+(geneBase<bool>& obj)
{
_ASSERT(code.size() == obj.GetCode()->size());
vector<bool> *_code_ = new vector<bool>();
for(uint i = 0; i < code.size(); i++)
{
if(obj.GetCode()->at(i))
_code_->push_back(true);
else
_code_->push_back(code.at(i));
}
geneBase<bool> *C = new geneBase<bool>();
C->SetCode(_code_);
return *C;
}
geneBase<bool>& geneBase<bool>::operator-(geneBase<bool>& obj)
{
_ASSERT(code.size() == obj.GetCode()->size());
vector<bool> *_code_ = new vector<bool>();
for(uint i = 0; i < code.size(); i++)
{
if(!(obj.GetCode()->at(i)))
_code_->push_back(false);
else
_code_->push_back(code.at(i));
}
geneBase<bool> *C = new geneBase<bool>();
C->SetCode(_code_);
return *C;
}
geneBase<bool>& geneBase<bool>::operator+=(geneBase<bool>& obj)
{
_ASSERT(code.size() == obj.GetCode()->size());
for(uint i = 0; i < code.size(); i++)
{
if(obj.GetCode()->at(i))
code.at(i) = true;
}
this->fitness = 0;
return *this;
}
geneBase<bool>& geneBase<bool>::operator-=(geneBase<bool>& obj)
{
_ASSERT(code.size() == obj.GetCode()->size());
for(uint i = 0; i < code.size(); i++)
{
if(!(obj.GetCode()->at(i)))
code.at(i) = false;
}
this->fitness = 0;
return *this;
}
#pragma endregion
secondo me in molti casi mi conviene lavorare con i puntatori, anche usando std::move come consigliato inizialmente, la velocità di C# rimane molto superiore, ma tuttavia, in c++ si dovrebber poter arrivare a prestazioni maggiori.
Non ho messo il codice inerente alla seconda classe, ma vale lo stesso discorso... dato che vi sono array di oggetti di tipo geneBase.
Io credo che l'operatore = sia uno dei problemi. credo mi convenga usare i puntatori, faccio un test e vi dico
Fossi in te:
Prima cosa:
geneBase<T>::geneBase(void)
Evita
geneBase<T>::geneBase()
E' corretto in C++
Costruttori semplici possono essere scritti come:
geneBase() : fitness(0), mutation_rate(0) { }
template <typename T>
geneBase<T>::geneBase(geneBase&& other)
{
code.assign(other.code.begin(), other.code.end());
fitness = other.fitness;
mutation_rate = other.mutation_rate;
}
Così facendo, fai una copia di code (non si capisce cosa sia dal codice che hai passato, immagino un altro vector)
Quando usi swap, move e così via, devi "ricorsivamente" usare lo stesso costrutto sui membri "complessi" della classe
template <typename T>
geneBase<T>::geneBase(geneBase&& other)
{
code = std::move(other.code);
fitness = other.fitness;
mutation_rate = other.mutation_rate;
}
template <typename T>
geneBase<T>::~geneBase(void)
{
code.erase(code.begin(),code.end());
}
Assolutamente no
Non c'è bisogno di fare questo, la vita di code è collegata alla vita di geneBase, appena verrà distrutto geneBase verrà chiamato in automatico il distruttore di code, che a sua volta chiamerà i distruttori degli ogetti interni
Gli unici distruttori che vanno chiamati nel distruttore del loro container sono per i puntatori, ma con:
delete puntatore;
geneBase<bool>& geneBase<bool>::operator+=(geneBase<bool>& obj)
{
_ASSERT(code.size() == obj.GetCode()->size());
for(uint i = 0; i < code.size(); i++)
{
if(obj.GetCode()->at(i))
code.at(i) = true;
}
this->fitness = 0;
return *this;
}
vector di bool in C++ è una specializzazione, in realtà è un bitset
Non credo che potrai sfruttare a tuo favore questa caratteristica, ti conviene scegliere un'altra maniera per rappresentare i tuoi dati
Quello che stai facendo qua, è veramente lento
code ha lunghezza variabile durante il ciclo di vita dell'oggetto?
Data la differenza (notevole) di velocità, credo che il punto sia proprio che nel programma C++ CLR che ho scritto si fa una copia totale tutte le volte, quindi nel complesso rallenta molto.
Mentre in C# queste cose essendo già gestite il problema non mi è emerso...
Dovrei provare a lavorare con i puntatori, che di norma ok non si fa, ma in questo caso dove si lavora ad intensità di dati, ovvero alla fine è un continuo modificare array, sia lacosa migliore.
Ora ho anche un problemino di memoria, ovvero da qualche parte non sto rilasciando correttamente, dal debug noto che gli algoritmi di ordinamento fanno qualche pasticcio, prob. il mio operatore di copia non va bene.
Per quanto riguarda il confronto fra C++ e C# trovai un bell'articolo qui:
http://www.codeproject.com/Articles/212856/Head-to-head-benchmark-Csharp-vs-NET
Dopo averti risposto al tuo ultimo reply, mi sento di dover rispondere qua:
Provengo anche io da C#, quindi credo che almeno in parte riusciremo a capirci a vicenda :D
Mentre in C# queste cose essendo già gestite il problema non mi è emerso...
Questa è una falsa illusione, quello che C# fa per il programmatore è solo eliminare gli oggetti "inutilizzati" non fa altro
Il comportamento che vedi non è causa CLR, ma causa layout dei tuoi oggetti
Mi spiego, prima discutevo con tomminno perchè diceva "la differenza è..."
Ed ovviamente questo è il punto centrale del discorso
C#, Java ed i tanti linguaggi OOP con gc fanno dimenticare all'utente una cosa importantissima: capire come i propri dati sono rappresentati in ram
Cos'è in C# un oggetto?
Un contenitore di puntatori
Le uniche cose che contiene veramente, sono i value type
Immaginati ora il tuo List di geneBase in C#
Cos'è un array di geneBase in C#? Un Array di puntatori
Significa:
[pointer, pointer, pointer...]
Cos'è geneBase?
Un misto di value type ed altri puntatori
geneBase { fitness, mutation_rate, pointerTo_CodeList }
Cos'è code?
Un puntatore, che al suo interno ha
{ capacity, size, ...pointerTo_BoolArray }
Cos'è un Array di Bool?
In C++ cos' è un array di geneBase?
[ { fitness, mutation_rate, { capacity, size, ..., pointerTo_Bitset }},
{ fitness, mutation_rate, { capacity, size, ..., pointerTo_Bitset }} ]
Quindi, alla fine, è anche meglio no?
No
Primo sta usando un bitset, quindi l'accesso ai singoli elmenti è LENTO
Secondo, non stai sfruttando il fatto che internamente vector conservi il puntatore ad un array - hai usato mode sulla tua classe, ma hai poi copiato i dati del bitset
Dovevi dare a tua volta move sul bitset - il bitset allora avrebbe sfruttato il fatto che internamente ha un puntatore ai dati
Ma scusa, C++ non è sempre meglio?
Non è il linguaggio a fare veloce un programma, ma gli algoritmi e il [B]layout dei propri dati
C++ permette di scrivere codice migliore rispetto a C# perchè hai pieno controllo del layout dei tuoi dati
Quando si scrive un sw ad alte performance, il layout dei dati è una cosa fondamentale da considerare
Oggi il collo di bottiglia di ogni software, è l'accesso ai dati
Se vuoi un software performance, devi prima di tutto pensare al design delle tue strutture dati e come sono rappresentate in memoria
MS al Build 2013 ha dedicato un talk al riguardo:
Native Code Performance and Memory: The Elephant in the CPU
http://channel9.msdn.com/Events/Build/2013/4-329
Nessun linguaggio può fare per te queste scelte, nessun compilatore
Sei tu che sei chiamato a farlo
C# ti impone una visione basata sui puntatori, che distrugge il data locality e quindi ogni speranza di avere buone performance
E' una illusione pensare che C# faccia scelte per te. Ti impone solo una strada che genericamente dovrebbe andar bene per la maggior parte dei casi
Se vuoi performance simili, devi usare solamente struct in C# che sono limitate e scomode da usare
Ecco perchè, discutevo con tomminno sul fatto che il problema parte da altro - e credo che alla fine entrambi dicevamo la stessa cosa
Altra cosa importante, i puntatori non sono il male
Se l'oggetto che usa il puntatore segue la regola dei tre http://en.wikipedia.org/wiki/Rule_of_three_%28C%2B%2B_programming%29 non c'è possibilità di trovarsi in problemi
Il problema si ha quando i puntatori non hanno un ciclo di vita chiaro al programmatore
Ovvero il programmatore non sa, come il proprio algoritmo dovrà funzionare (incapacità? Troppa complessità?) e quindi si eliminano puntatori prima, si accedono a puntatori ormai eliminati, non si eliminano proprio, e così via
Come vedi, la riflessione non è su C# vs C++
Ma più che altro un problema di programmazione pura:
Gestione delle proprie strutture dati, layout dei dati, pattern di accesso ai dati, algoritmi
Teo@Unix
08-07-2013, 13:44
Grazie della risposta approfondita! Utilissima.
In realtà vengo da C, ciò che mi ha messo in difficoltà nel C++ è che conosco ancora poco come organizza i miei dati e come si comportano le librerie STL nei confronti della memoria allocata.. è la prima volta che uso strutture come vector<> in C++.
difatti io di solito uso sempre puntatori, ma molto spesso vedo codice di persone che non ne fanno assolutamente uso in C++.
Detto questo,
penso di dover correggere i vari operatori e costruttori, quindi riepilogando, move() è la soluzione sempre preferibile per copiare ?
GRazie per i vari link che ora mi guarderò.
Sono contento che ti sia interessato sopratutto al primo link
E' un argomento sottovalutato, ma di estrema importanza per chi lavora con certo sw
E' una bella inotroduzione a:
What Every Programmer Should Know About Memory
Del famoso troll Drepper, mantainer delle Glibc
http://www.akkadia.org/drepper/cpumemory.pdf
Sì, in C++ si riesce a contenere l'uso dei puntatori, e più che altro renderlo "semplice"
Per tornare in più in tema:
http://www.cplusplus-soup.com/2012/10/value-semantics-and-polymorphism.html
(ing Google)
In poche parole: implementa sempre move
Se dovesse continuare ad essere un problema, devi affrontare il problema in una maniera differente
E' molto più bravo di me, ovviamente, nel dire tutto ciò che c'è sa sapere e riferimenti e libri utili
Teo@Unix
08-07-2013, 15:14
Grazie ancora, cerco di sistemare il codice e studiare.
Teo@Unix
08-07-2013, 16:31
Altra cosa, per esempio:
vector<geneBase<T>> population
vector<geneBase<T>> *new_population;
...
population = move(*population_new);
uso move() con 2 vector<>. Il contenuto di population dove finisce?
Perchè dalla memoria allocata risulta che essa non ritorna all'ammontare precedente ma rimane qualcosa.
Altra cosa, per esempio:
vector<geneBase<T>> population
vector<geneBase<T>> *new_population;
...
population = move(*population_new);
uso move() con 2 vector<>. Il contenuto di population dove finisce?
Perchè dalla memoria allocata risulta che essa non ritorna all'ammontare precedente ma rimane qualcosa.
Dopo aver dato uno sguardo ai sorgenti della STL di GCC 4.8 posso confermarti che:
- Viene chiamato il move assignment di population
- Vengono scambiati i 3 puntatori che usa vector internamente
- Viene chiamato il distruttore di ogni elemento contenuto in population_new (che ora contiene i dati di population)
L'overhead del move di un vector è solo quindi, lo swap di 3 puntatori :sofico:
Però, non viene chiamato alcun delete population_new
Devi informare l'allocatore che la memoria occupata da population_new non ti serve più
Move lascia l'oggetto in uno stato non definito ma comunque valido, quindi va sempre e comunque chiamato delete
Così come viene comunque chiamato in automatico il distruttore di qualcosa che è stack allocated, anche se si ha usato move
Occhio che con C# difficilmente copi il contenuto (anzi copiare un oggetto in C# è proprio un incubo, alla fine sei costretto a serializzare e deserializzare un oggetto per clonarlo), più facile che tu abbia semplicemente copiato il riferimento.
Se le performance non sono un problema ti fa un extension method che scorre i proprieta' dell'oggetto e le copia in quello di destinazione. Un po' crude, ma efficace.
Se le performance non sono un problema ti fa un extension method che scorre i proprieta' dell'oggetto e le copia in quello di destinazione. Un po' crude, ma efficace.
Attenzione che una extension method non ha accesso ai dati privati della classe
Grazie della risposta approfondita! Utilissima.
In realtà vengo da C, ciò che mi ha messo in difficoltà nel C++ è che conosco ancora poco come organizza i miei dati e come si comportano le librerie STL nei confronti della memoria allocata.. è la prima volta che uso strutture come vector<> in C++.
Un vector non e' altro che un wrapper attorno ad un segmento contiguo di memoria dove sono memorizzati i valori; ordinarlo vuol dire copiarne gli elementi. La differnza col C e' che in questo vengono chiamati (se necessario) operatori di copia.
difatti io di solito uso sempre puntatori, ma molto spesso vedo codice di persone che non ne fanno assolutamente uso in C++.
Diciamo che e' meno necessario, ma dipende da come organizzi il codice.
Detto questo,
penso di dover correggere i vari operatori e costruttori, quindi riepilogando, move() è la soluzione sempre preferibile per copiare ?
No, e' la soluzione preferibile per muovere, altrimenti si chiamerebbe copy() ;). Con la move l'oggetto di origine non contiene piu' l'oggetto originale (ma deve essere valido, perche' verra' distrutto).
Attenzione che una extension method non ha accesso ai dati privati della classe
Vero, me n'ero dimenticato
Altra cosa, per esempio:
vector<geneBase<T>> population
vector<geneBase<T>> *new_population;
...
population = move(*population_new);
uso move() con 2 vector<>. Il contenuto di population dove finisce?
Dipende da come hai implementato l'operatore di move.
La soluzione piu' pratica in questi casi e' "scambiare" di posto gli attributi dele due istanze: nel caso di un vector<> potrebbe essere qualcosa tipo:
vector<T>& operator=(vector<T>&& v)
{
std::swap(ptr, v.ptr);
std::swap(size, v.size);
return *this;
}
In questo modo quando verra' chiamato il distruttore dell'oggetto di origine verra' liberata la memoria dell'oggetto che hai sovrascritto con la move.
tomminno
09-07-2013, 16:38
Se le performance non sono un problema ti fa un extension method che scorre i proprieta' dell'oggetto e le copia in quello di destinazione. Un po' crude, ma efficace.
Si se non fosse che "copia" in C# si intende sempre copia del riferimento (se non sono value type), quindi per copiare veramente una classe (che potrebbe contenere un'altra classe che a sua volta ne contiene un'altra...) con tutto il suo contenuto il metodo più rapido è serializzarla e deserializzarla, metodo che funziona anche se la classe viene modificata.
Si se non fosse che "copia" in C# si intende sempre copia del riferimento (se non sono value type), quindi per copiare veramente una classe (che potrebbe contenere un'altra classe che a sua volta ne contiene un'altra...) con tutto il suo contenuto il metodo più rapido è serializzarla e deserializzarla, metodo che funziona anche se la classe viene modificata.
Beh ma dipende se ti serve una copia in profondita' o piatta. A seconda dei casi opererai differenemente, copiando solo i riferimenti o facendo cloni anche degli oggetti collegati (qui diventa piu' difficile perche' devi stare attento ai riferimenti circolari).
Alla fine la serializzazione penso non faccia altro che scorrere gli elementi con la reflection; quindi una alternativa e' fare la cosa da se' per evitare il doppio lavoro.
Attenzione che una extension method non ha accesso ai dati privati della classe
Mi son fatto venire il dubbio... vale anche usando la reflection ? Non ne sono piu' convinto. E' vero che quando ho usato io la cosa copiavo degli attributi pubblici perche' erano l'unica cosa da copiare, ma mi sembra strano non sia possibile.
Non uso tanto spesso C# per cui potrei sbagliarmi... domani controllo il codice che ho scritto a suo tempo.
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.