Ottimizzare C++/Ottimizzazione del codice C++: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Riga 3:
== Come ridurre le operazioni di allocazione e deallocazione ==
 
=== La funzione ''<code>alloca''</code> ===
 
'''In funzioni non-ricorsive, per allocare spazio di dimensione variabile ma non grande, usa la funzione "<code>alloca"</code>.'''
 
È molto efficiente, in quanto alloca spazio sullo stack.
Riga 23:
Analoga ottimizzazione va fatta per le operazioni che provocano allocazioni indirettamente, come la copia di oggetti che, direttamente o indirettamente, possiedono memoria dinamica.
 
=== La funzione ''<code>reserve''</code> ===
 
'''Prima di aggiungere elementi a oggetti ''<code>vector''</code> o ''<code>string''</code>, chiama ''<code>reserve''</code> con una dimensione sufficiente per la maggior parte dei casi.'''
 
Se si aggiungono ripetutamente elementi a oggetti ''<code>vector''</code> o ''<code>string''</code>, ogni tanto viene eseguita una costosa operazione di riallocazione del contenuto.
Per evitare tali costose riallocazioni, basta allocare inizialmente lo spazio che probabilmente sarà sufficiente.
 
=== Mantenere la capacità dei ''<code>vector''</code> ===
 
'''Per svuotare un oggetto ''<code>vector<T> x''</code> senza deallocarne la memoria, usa l'istruzione ''<code>x.resize(0);''</code>; per svuotarlo deallocandone la memoria, usa l'istruzione ''<code>vector<T>().swap(x);''</code>.'''
 
Per svuotare un oggetto ''<code>vector''</code>, esiste anche l'istruzione ''<code>clear()''</code>; tuttavia lo standard non specifica se tale istruzione conserva la capacità (capacity) del ''<code>vector''</code> oppure no.
Se questa istruzione deve essere eseguita spesso, e si vuole essere sicuri di evitare frequenti riallocazioni, si deve chiamare la funzione ''<code>resize''</code>, che, secondo lo standard, conserva sicuramente la capacità.
Se invece si vuole essere sicuri di liberare la memoria usata da tale collezione, in quanto per un po' di tempo tale oggetto non verrà più usato, oppure verrà usato con un numero molto più piccolo di elementi, si deve chiamare la ''funzione <code>swap''</code> su un nuovo oggetto temporaneo vuoto.
 
=== Ridefinire ''la funzione <code>swap''</code> ===
 
'''Per ogni classe concreta <code>T</code> che, direttamente o indirettamente, possiede della memoria dinamica, ridefinisci le appropriate funzioni ''<code>swap''</code>.'''
 
In particolare, aggiungi alla classe una funzione membro ''<code>public''</code> con la seguente firma:
 
In particolare, aggiungi alla classe una funzione membro ''public'' con la seguente firma:
<source lang=cpp>void swap(T&) throw();</source>
 
e aggiungi la seguente funzione non-membro nello stesso namespace che contiene la classe <code>T</code>:
 
<source lang=cpp>void swap(T& lhs, T& rhs) { lhs.swap(rhs); }</source>
 
e, se la classe non è un template di classe, aggiungi la seguente funzione non-membro nello stesso file in cui è dichiarata la classe <code>T</code>:
 
<source lang=cpp>namespace std { template<> swap(T& lhs, T& rhs) { lhs.swap(rhs); } }</source>
 
Nella libreria standard la funzione ''std::swap'' viene richiamata in molti algoritmi. Tale funzione ha una implementazione generica e specializzazioni per vari tipi della libreria standard.
 
Se gli oggetti di una classe non-standard vengono usati in algoritmi della libreria standard, e non viene fornito un overload della funzione ''<code>swap''</code>, viene usata l'implementazione generica.
 
L'implementazione generica di <code>swap</code> comporta la creazione e distruzione di un oggetto temporaneo e l'esecuzione di due assegnamenti di oggetti. Tali operazioni richiedono molto tempo se applicate ad oggetti che possiedono della memoria dinamica, in quanto tale memoria viene riallocata per tre volte.
 
Il possesso di memoria dinamica citato nella linea-guida può essere anche solo indiretto. Per esempio, se una variabile membro è un oggetto string o vector, o è un oggetto che contiene un oggetto string o vector, la memoria posseduta da tali oggetti viene riallocata ogni volta che si copia l'oggetto che li contiene. Quindi anche in tali casi si devono ridefinire le funzioni ''<code>swap''</code>.
 
Se l'oggetto non possiede memoria dinamica, la copia dell'oggetto è molto più veloce, e comunque non sensibilmente più lenta che usando altre tecniche, e quindi non si devono ridefinire funzioni ''<code>swap''</code>.
 
Se la classe è astratta, la funzione ''<code>swap''</code> non dovrebbe mai essere chiamata sugli oggetti di tale tipo, e quindi anche in questo caso non si devono ridefinire funzioni ''<code>swap''</code>.
 
Per velocizzare la funzione <code>swap</code>, la si deve specializzare per la propria classe. Ci sono due modi possibili: nel namespace della stessa classe (che può essere quello globale) come overload, o nel namespace std come specializzazione del template standard. È meglio definirla in entrambi i modi, in quanto, in primo luogo, se si tratta di un template di classe solo il primo modo è possibile, e poi alcuni compilatori non accettano o segnalano un avvertimento se è definita solo nel primo modo.
 
L'implementazione di tali funzioni devono accedere a tutti i membri dell'oggetto, e quindi hanno bisogno di richiamare una funzione membro, che per convenzione si chiama ancora swap, che effettua il lavoro.
Line 67 ⟶ 72:
Tale lavoro deve consistere nell'effettuare lo swap di tutti i membri non-static dei due oggetti, tipicamente chiamando la funzione swap su di essi, senza qualificarne il namespace.
 
Per consentire di trovare la funzione ''<code>std::swap''</code>, la funzione deve iniziare con la seguente istruzione:
 
<source lang=cpp>using std::swap;</source>
Line 73 ⟶ 78:
== Come ridurre i costrutti che richiamano routine del supporto run-time ==
 
=== L'operatore ''<code>typeid''</code> ===
 
'''Invece di usare l'operatore ''<code>typeid''</code>, usa una funzione ''<code>virtual''</code>.'''
 
Tale operatore può richiedere un tempo superiore a quello richiesto da una semplice chiamata a funzione.
 
=== L'operatore ''<code>dynamic_cast''</code> ===
 
'''Invece dell'operatore ''<code>dynamic_cast''</code>, usa l'operatore ''<code>typeid''</code>, o, ancora meglio, una funzione ''<code>virtual''</code>.'''
 
Tale operatore può richiedere un tempo notevolmente superiore a quello richiesto da una semplice chiamata a funzione, e maggiore anche a quello richiesto dall’operatore ''<code>typeid''</code>.
 
=== La specifica di eccezioni vuota ===
 
'''Usa la specifica di eccezioni vuota (cioè aggiungi "<code>throw()"</code> dopo la dichiarazione) alle funzioni di cui sei certo che non solleveranno eccezioni.'''
 
Alcuni compilatori utilizzano tale informazione per ottimizzare la contabilità necessaria per gestire le eventuali eccezioni.
 
=== Il costrutto ''<code>try/catch''</code> ===
 
'''Sposta prima dei colli di bottiglia le istruzioni ''<code>try''</code>, e dopo i colli di bottiglia le corrispondenti istruzioni ''<code>catch''</code>.'''
 
L’esecuzione di un blocco <code>try/catch</code> talvolta ha costo zero, ma altre volte comporta un rallentamento. Evita di eseguire tale blocco all’interno dei colli di bottiglia.
 
=== Operazioni a virgola mobile ===
Line 120 ⟶ 125:
=== Verifica di intervallo ===
 
'''Per controllare se un numero intero ''<code>i''</code> è compreso tra ''<code>min_i''</code> e ''<code>max_i''</code>, estremi inclusi, usa l'espressione ''<code>unsigned(i – min_i) <= unsigned(max_i – min_i)''</code>.'''
 
La suddetta formula è equivalente alla seguente formula, più intuitiva:
Line 128 ⟶ 133:
Quest'ultima formula richiede due confronti, mentre quella suggerita ne richiede uno solo.
 
In particolare, per controllare che un numero ''<code>i''</code> sia valido come indice per accedere a un array di ''<code>size''</code> elementi, si può usare la seguente espressione:
 
<source lang=cpp>unsigned(i) < unsigned(size)</source>
 
Se le espressioni utilizzate sono già tidi un tipo ''<code>unsigned''</code>, le conversioni non sono necessarie.
 
=== Ordine dei casi di ''istruzioni <code>switch''</code> ===
 
'''Nelle istruzioni <code>switch</code>, poni i casi in ordine di probabilità decrescente.'''
 
Nella linea-guida 3.1.12 si consigliava di porre prima di casi più tipici, cioè quelli che si presume siano più probabili.
Line 180 ⟶ 185:
 
Supponi che stai scrivendo la seguente classe base di libreria:
 
<source lang=cpp>
class Base {
Line 187 ⟶ 193:
};
</source>
 
In questa classe, la funzione g esegue un algoritmo che usa una chiamata a f come punto di personalizzazione dell'algoritmo. Lo scopo è consentire di scrivere il seguente codice applicativo:
 
<source lang=cpp>
class Derivata1: public Base {
Line 197 ⟶ 205:
p1->g();
</source>
 
In tal caso, è possibile convertire il precedente codice di libreria nel seguente:
 
<source lang=cpp>
template <class Derivata> class Base {
public:
void f() { static_cast<Derivata*>(this)->f(); }
void g() { f(); }
};
</source>
 
Il codice applicativo, di conseguenza, diventerà il seguente:
 
<source lang=cpp>
class Derivata1: public Base<Derivata1> {
public:
void f() { ... }
};
...
Derivata1* p1 = new Derivata1;
p1->g();
</source>
 
In tal modo, si ha binding statico della funzione membro <code>Derivata1::f</code> alla funzione membro <code>Base<Derivata1>::g</code>, cioè la chiamata a tale funzione non è di tipo ''<code>virtual''</code>.
 
Tuttavia, con questa soluzione, se si volesse aggiungere la seguente definizione
 
<source lang=cpp>
class Derivata1: public Base<Derivata1> {
public:
void f() { ... }
};
</source>
 
non risulterebbe possibile definire un puntatore o riferimento a una classe base comune a <code>Derivata1</code> e <code>Derivata2</code>, in quanto tali classi risultano due tipi senza alcuna relazione; quindi questa soluzione non è adatta se si vuole permettere al codice applicativo di definire un contenitore di oggetti derivati da Base.
 
Altre limitazioni sono le seguenti:
* <code>Base</code> è necessariamente un tipo astratto;
* un oggetto di tipo </code>Derivata1</code> non può essere convertito in un oggetto di tipo <code>Derivata2</code> o viceversa;
* per ogni derivazione di <code>Base</code> tutto il codice di <code>Base</code> viene duplicato.
 
=== Il pattern ''Strategy'' ===
 
'''Se un oggetto che implementa il pattern ''Strategy'' (dettonoto anche come ''Policy'') è una costante in ogni algoritmo nel codice applicativo, ma il codice di libreria deve poter gestire più strategie, elimina tale oggetto, rendi ''<code>static''</code> tutti i suoi membri, e aggiungi tale classe come parametro di template.'''
 
Supponi che stai scrivendo il seguente codice di libreria, che implementa il design pattern ''Strategy'':
 
<source lang=cpp>
class C;
class Strategy {
public:
virtual bool is_valid(const C&) const = 0;
virtual void run(C&) const = 0;
};
 
class C {
public:
void set_strategy(const Strategy& s): s_(s) { }
void f() { if (s.is_valid(*this)) s.run(*this); }
private:
Strategy s_;
};
</source>
 
Line 257 ⟶ 272:
 
<source lang=cpp>
class MyStrategy: public Strategy {
public:
virtual bool is_valid(const C& c) const { ... }
virtual void run(C& c) const { ... }
};
...
MyStrategy s; // Oggetto che rappresenta la strategia.
 
MyStrategyC sc; // Oggetto checon rappresentastrategia la strategiapersonalizzabile.
C c.set_strategy(s); // OggettoAssegnazione condella strategia personalizzabile.
c.set_strategyf(s); // AssegnazioneEsecuzione dellacon strategia assegnata.
c.f(); // Esecuzione con strategia assegnata.
</source>
 
Line 273 ⟶ 287:
 
<source lang=cpp>
template <class Strategy>
class C {
public:
void f() { if (Strategy::is_valid(*this)) Strategy::run(*this); }
};
</source>
 
Il codice applicativo, di conseguenza, diventerà il seguente:
 
</source lang=cpp>
class MyStrategy {
public:
static bool is_valid(const C<MyStrategy>& c) { ... }
static voidbool runis_valid(const C<MyStrategy>& c) { ... }
static boolvoid is_validrun(const C<MyStrategy>& c) { ... }
};
...
 
C<MyStrategy> c; // Oggetto con strategia preassegnata.
c.f(); // Esecuzione con strategia preassegnata.
</source>
 
In tal modo, si evita l'oggetto-strategia, e si ha il binding statico delle funzioni membro <code>MyStrategy::is_valid</code> e <code>MyStrategy::run</code>, cioè si evitano chiamate a funzioni virtuali<code>virtual</code>.
 
Tuttavia, tale soluzione non consente di decidere la strategia in fase di esecuzione, e tanto meno di cambiarla durante la vita dell'oggetto, e, inoltre, duplica il codice della strategia per ogni istanziazione di tale classe.
Line 335 ⟶ 351:
=== Operatore di assegnamento ===
 
'''In un overload dell'operatore di assegnamento (operator=), se sei sicuro che non solleverà eccezioni, non usare la tecnica “copy''copy&swap”swap'', che crea una copia temporanea dell’oggetto sorgente, ma copia i singoli membri.'''
 
La tecnica più efficiente per copiare un oggetto è imitare il costruttore di copia, cioè prima copiare gli oggetti base e poi gli oggetti membro, in ordine di dichiarazione.
 
Tuttavia, tale tecnica non è “exception''exception-safe”safe'', cioè corre il rischio di non chiamare il distruttore di qualche oggetto nel caso venga sollevata un’eccezione durante la copia. Pertanto, se c’è la possibilità che durante la copia venga sollevata un’eccezione, si deve usare una tecnica “exception''exception-safe”safe'', che tuttavia non avrà prestazioni ottimali. La tecnica di assegnamento “exception-safe” più elegante è quella detta “copy''copy&swap”swap'', esemplificata dal seguente codice:
<source lang=cpp>
C& C::operator=(C new_value) {
Line 357 ⟶ 373:
Talvolta è possibile creare un overload della funzione originale che prende un argomento proprio del tipo corrispondente, evitando quindi la conversione.
 
== 5.5. Come ottimizzare l’uso della pipeline del processore ==
 
Le istruzioni in linguaggio macchina di salto condizionato (chiamate anche ''branch''), possono essere generate dalla compilazione delle istruzioni C++ <code>if-else</code>, <code>for</code>, <code>while</code>, <code>do-while</code>, <code>switch-case</code>, e dell'operatore di espressione condizionale ("<code>?:"</code>).
 
I moderni processori elaborano efficientemente i salti condizionati solo se riescono a prevederli.
Line 378 ⟶ 394:
 
Ossia, se ha del codice come il seguente, in cui ''c'' e ''d'' rappresentano espressioni costanti e ''b'' rappresenta un'espressione booleana:
 
<source lang=cpp>
a = b ? c : d;
Line 411 ⟶ 428:
Per esempio, in un loop, la seguente istruzione:
 
<source lang=cpp>a = *++p;</source>
a = *++p;
</source>
 
è meno efficiente della seguente:
 
<source lang=cpp>a = *p++;</source>
a = *p++;
</source>
 
Nel primo caso il valore del puntatore è calcolato appena prima di accedere all'oggetto puntato, mentre nel secondo caso è calcolato nell'iterazione precedente.
Line 434 ⟶ 447:
Analogo effetto si ha per i dati statici dichiarati in tale codice.
 
=== Le ''<code>union''</code> ===
 
'''In array medi o grandi, usa le ''<code>union''</code>.'''
 
Le <code>union</code> permettono di risparmiare memoria in strutture di tipi variabili e quindi di renderle più compatte.
Non usarle in oggetti piccoli o piccolissimi, in quanto non si hanno vantaggi significativi per il risparmio di memoria, e con alcuni compilatori le variabili poste nella union non vengono tenute nei registri del processore.
 
Line 447 ⟶ 460:
I bit-field riducono le dimensioni dell'oggetto, e quindi permettono di far stare più oggetti nella cache dei dati e nella memoria fisica, riducendo, rispettivamente, il ricorso alla memoria principale e al disco fisso.
 
Per esempio, per memorizzare un bit e tre numeri che possono essere compresi tra 0 e 1000, puoi usare un campo ''<code>unsigned''</code> di un bit e tre campi ''<code>unsigned''</code> di 10 bit ciascuno (1 + 10 + 10 + 10 = 31, 31 <= 32), mentre per memorizzare cinque numeri che possono essere compresi tra -20 e +20, puoi usare cinque campi ''signed'' di 6 bit ciascuno.
 
=== Codice di template non dipendente dai parametri ===
Line 563 ⟶ 576:
 
<source lang=cpp>
f *= pow(2, n);
</source>
 
Line 569 ⟶ 582:
 
<source lang=cpp>
if (*(int*)&f & 0x7FFFFFFF) { // se f==0 non fare niente
*(int*)&f += n << 23; // aggiungi n all’esponente
}
</source>
 
Line 606 ⟶ 619:
=== Divisione intera per costanti ===
 
'''Se un numero intero ''<code>signed''</code> è sicuramente non-negativo, quando lo dividi per una costante, convertilo in ''<code>unsigned''</code>.
 
Se ''<code>s''</code> è un intero ''<code>signed''</code>, ''<code>u''</code> è un intero ''<code>unsigned''</code>, e ''<code>c''</code> è un'espressione costante intera (positiva o negativa), l'operazione ''<code>s / c''</code> è più lenta di ''<code>u / c''</code> e l'operazione ''<code>s % c''</code> è più lenta di ''<code>u % c''</code>, sia quando ''<code>c''</code> è una potenza di due, sia quando non lo è, in quanto nei primi due casi si deve tenere conto del segno.
 
D'altra parte, la conversione da <code>signed</code> a <code>unsigned</code> non costa niente, in quanto èsi tratta solo di una reinterpretazione degli stessi bit.
Se ''s'' è un intero ''signed'', ''u'' è un intero ''unsigned'', e ''c'' è un'espressione costante intera (positiva o negativa), l'operazione ''s / c'' è più lenta di ''u / c'' e l'operazione ''s % c'' è più lenta di ''u % c'', sia quando ''c'' è una potenza di due, sia quando non lo è, in quanto nei primi due casi si deve tenere conto del segno.
Pertanto, se <code>s</code> è un intero ''signed''con segno, che sai essere sicuramente positivo o nullo, ne velocizzi la divisione se usi le seguenti espressioni equivalenti: ''<code>unsigned(s) / c''</code> e ''<code>unsigned(s) % c''</code>.
D'altra parte, la conversione da signed a unsigned non costa niente, in quanto è solo una reinterpretazione degli stessi bit.
Pertanto, se s è un intero ''signed'' che sai essere sicuramente positivo o nullo, ne velocizzi la divisione se usi le seguenti espressioni equivalenti: ''unsigned(s) / c'' e ''unsigned(s) % c''.
 
=== Processori con bus dati ridotto ===