Newsgroup: it.comp.lang.c++
Titolo: Accelerare operator+ e simili

Ciao,
	altra domandina rompipalle.....
Nel mio potentissimo :) programma di integrazione tutto ruota attorno ad
una classe da me definita, nvector, che gestisce vettori di lunghezza
arbitraria (ma non "troppo variabile"), permettendomi tutta una serie
di operazioni matematiche che mi fanno comodo.
No, non ho fatto un template perche' tanto uso solo double.
Il programma e' al punto in cui comincio a guardare l'output del
profiler, per cercare di capire dove e' che spendo il mio tempo.
Le prime 4 linee sono:
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  ms/call  ms/call  name    
 57.60    282.86   282.86   448436     0.63     0.63  spacelsacoup::eval(nvector *, double const &, nvector const &)
 17.72    369.88    87.02   782815     0.11     0.11  nvector::operator+(nvector const &) const
 13.31    435.24    65.36   656796     0.10     0.10  operator*(double const &, nvector const &)
  3.65    453.14    17.90   156732     0.11     0.11  nvector::operator+=(nvector const &)
Che la eval sia un cesso lo so benissimo, appena sono sicuro che le
formule sono corrette la riscrivo in modo "illegale" accedendo
direttamente ai dati interni dei vettori con dei puntatori in modo da
guadagnare su tutte le indirezioni mediante operator[] (che e' inline,
BTW).
Resta pero' il fatto che la seconda e terza linea indicano che
parecchio tempo viene speso anche in quei due operatori.
Qualcuno ha consigli su come potrei accelerare quelle funzioni?
(nota: la lunghezza nel caso in questione e' sui 500 elementi).
Il codice di operator+:
nvector nvector::operator+(const nvector& n2) const
{
  nvector r(len);
#ifndef NVECTOR_FASTUNSAFE
  if (len != n2.len) {
    nvector_handler(NVECTOR_LEN_MISMATCH);
    return r;
  }
#endif
  register int l = len;
  register double* v1 = vals;
  register double* v2 = n2.vals;
  register double* vr = r.vals;
  while (l--)
    *vr++ = *v1++ + *v2++;
  return r;
}
Il codice di operator* e' moooolto simile (come si puo' facilmente
immaginare).
Ciao,
Alberto
-- 
Alberto BARSELLA
PGP fingerprint = 13 3F 22 D2 0B 0A D3 25  F1 89 FE B5 82 AD 75 2A
** Beliefs are dangerous. Beliefs allow the mind to stop functioning.
A non-functioning mind is clinically dead.  Believe in nothing... **

   nvector r(len);
magari il problema è nel costruttore?
Giusto un'idea, anche perchè NON sono un programmatore C++.
Ciao, julio di egidio
ju...@webzone.it

 magari il problema è nel costruttore?
Ottima intuizione :)
All'inizio infatti nella lista del tempo speso il costruttore di
nvector era sulla seconda linea (e gli altri piu' giu' di 1).
Ora lo ho modificato, velocizzando l'azzeramento con un memset e la
situazione e' quella che ho postato....


situazione e' quella che ho postato....
Mah, dubito che sia il punto-caldo per vettori di soli 500
elementi circa, ma quando fai "return r;" implichi, naturalmente
e purtroppo, un copy constructor.  Puo` forse essere che esso
sia in linea e quindi il profiler non te ne evidenzi i costi?
Se cosi` e`, forse puoi guadagnare qualcosa facendo di nvector
una classe con "value semantics" -- una "busta" (``envelope'')
che contiene solo un puntatore ad una "lettera" (``letter'') la
quale a sua volta contiene un reference count.  A questo punto
il copy-ctor diventa velocissimo (quindi ad esempio gli argomenti
di tipo nvector li passeresti per valore, non piu` per const&)
a prezzo di una indirettezza in piu` (parzialmente evitabile in
questo caso, a mio parere, con uno strano idioma che ho visto
usare in certe implementazioni di std::string) e soprattutto di
una certa complicazione del codice.
Che funzioni piu` svelto e` tutto da vedere, comunque ecco
l'idea-base (scrivo tutto inlinea solo per compattezza...!):
class nvector;
class nvecrep {
friend class nvector;
    size_t len, ref;
    double* data() { return reinterpret_cast<double*>(this + 1); }
    double* grab() { ++ref; return data(); }
    void release () { if (--ref == 0) delete this; }
    static void* operator new(size_t objsize, size_t veclen) {
	return ::operator new(objsize+veclen*sizeof(double));
    }
    static nvecrep* create(size_t len) {
	nvecrep* result = new(len) nvecrep;
	result->len = len;
	result->ref = 1;
	return result;
    }
    nvecrep* clone(double* &datap) {
	if(ref==1) return this;
	nvecrep* result = create(len);
	--ref;
	memcpy(result->data(), data(), len*sizeof(double));
	datap = data;
	return result;
    }
};
class nvector {
    nvecrep* rep;
    double* data;  // sempre == rep->data(), solo x ottimizzazione
public:
    explicit nvector(size_t len) {
	rep = nvecrep::create(len);
	data = rep->data();
    }
    ~nvector() { rep->release(); }
    nvector(const nvector& other) {
	data = other.rep->grab();
	rep = other.rep;
    }
    nvector& operator=(nvector other) {
	data = other.rep->grab();
	rep->release();
	rep = other.rep;
	return *this;
    }
    // esempio di funzione-membro che non modifica *this
    nvector operator+(nvector other) const {
	if(len!=other.len) throw "errore di lunghezza";
	nvector result(len);
	double *dres = result.data;
	// nb: probabilmente + veloce avanzare 1 indice
	// che 3 puntatori, comunque da verificare...!
	for(int i=0; i<len; ++i)
	    dres[i] = data[i]+other.data[i];
	return result;
    }
    // esempio di funzione-membro che modifica *this
    nvector& operator+=(nvector other) {
	if(len!=other.len) throw "errore di lunghezza";
	rep = rep->clone(data);
        for(int i=0; i<len; ++i)
	    data[i] += other.data[i];
    }
    // ecc ecc
};
Il problema sottile con questo approccio e` l'operator[].  Se
basta un "double operator[](size_t index) const", nessun
problema, un "{ return data[index]; }" sara` sufficiente, ma
in questo caso non sarebbe possibile scrivere:
	nvector nv(100);
	nv[7] = 45.62;
in quanto il risultato dell'operator[] sarebbe solo un rvalue;
occorre aggiungere ed usare una esplicita setElement tipo:
	void setElement(size_t index, double value) {
	    data[index] = value;
	}
e poi scrivere
	nv.setElement(7, 45.62);
Aggiungere un "double& operator[](size_t index)" rischierebbe
di lasciar erroneamente modificare altri nvector che condividono
lo spazio dati con questo, o se ci si copre da questo con un
"rep = rep->clone(data);" allora questo puo` danneggiare seriamente
le prestazioni (inutile sdoppiamento dei dati anche quando non
servirebbe).  Di solito ci si copre da questo inventando un'altra
classe ancora, con funzione di "proxy", da tornare dall'operator[];
questa tiene un puntatore all'nvector di partenza e copia dell'indice,
implementa un operator double(){ return pNvec.data[sIndex]; }, e un
operator=(double) che forza il vettore originale a clonare la propria
rappresentazione se necessario prima di chiamare setElement.  Secondo
me nel 99% dei casi queste sono complicazioni (e appesantimenti, e
rallentamente) inutili, e l'approccio con operator[] solo rvalue e
la setElement esplicita e` piu` semplice, piu` rapido, e migliore...
a meno che naturalmente non si abbia un milione di righe di codice
gia` scritte sull'ipotesi che operator[] e` un lvalue...:-).
Alex
--
 ____    Alex Martelli, Bologna, Italia -- email: 
al...@magenta.com
 \SM/___                                 
http://magenta.com/~alex
  \/\bi/     You never know what is enough unless you know what
     \/          is more than enough.