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

Contenuto cancellato Contenuto aggiunto
RamaccoloBot (discussione | contributi)
Riportate modifiche da en.wikibooks
Riga 2:
 
Anche i costrutti che generano del codice espanso ''inline'' possono avere un costo significativo, in quanto tali istruzioni devono pur essere eseguite.
In questa sezione si descrivono le tecniche per ridurre il numero complessivo di istruzioni che il processore dovrà eseguire per eseguire una data operazione.
 
In questa sezione si descrivono le tecniche per ridurre il numero complessivo di istruzioni che il processore dovrà eseguire per eseguirecompiere una data operazione.
=== Verifica di intervallo ===
 
=== Ordine dei casi diin istruzioni <code>switch</code> ===
'''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:
 
<source lang=cpp>min_i <= i && i <= max_i</source>
 
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>
 
Ovviamente, se le espressioni utilizzate sono già di 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 "Ordine dei casi dell'istruzione <code>switch</code>" del capitolo 3.1, si consigliava già di porre prima di casi più tipici, cioè quelli che si presume siano più probabili.
Come ulteriore ottimizzazione, si dovrebbepotrebbe contare, in esecuzioni tipiche, il numero di volte in cui viene eseguito ognuno dei singoli casi, e porre i casi in ordine da quello eseguito più volte a quello eseguito meno volte.
 
=== Parametri interi di template ===
Line 33 ⟶ 18:
Supponi che stai scrivendo la seguente funzione di libreria, in cui sia <code>x</code> che <code>y</code> non hanno un valore definito in fase di sviluppo della libreria:
 
<source lang=cpp>
int f1(int x, int y) { return x * y; }
</source>
 
Tale funzione può essere chiamata dal seguente codice applicativo, nel quale <code>x</code> non ha un valore costante, ma <code>y</code> è la costante 4:
 
<source lang=cpp>
int a = f1(b, 4);
</source>
 
Se, mentre scrivi la libreria, sai che il chiamante ti passerà sicuramente una costante intera come argomento <code>y</code>, puoi trasformare la tua funzione nel seguente template di funzione:
 
<source lang=cpp>
template <int Y> int f2(int x) { return x * Y; }
</source>
 
Tale funzione può essere chiamata dal seguente codice applicativo:
 
<source lang=cpp>
int a = f2<4>(b);
</source>
 
Tale chiamata istanzia automaticamente la seguente funzione:
 
<source lang=cpp>
int f2(int x) { return x * 4; }
</source>
 
Quest'ultima funzione è più veloce della precedente funzione <code>f1</code>, per i seguenti motivi:
* Viene passato un solo parametro alla funzione (<code>x</code>) invece di due (<code>x</code> e <code>y</code>).
* La divisionemoltiplicazione per una costante intera (4) è più veloce della divisionemoltiplicazione per una variabile intera (<code>y</code>).
* Dato che il valore costante (4) è una potenza di due, il compilatore, invece di eseguire una moltiplicazione intera, esegue uno scorrimento di bit.
 
In generale, i parametri di template interi sono delle costanti per chi istanzia il template e quindi per il compilatore, e le costanti sono gestite in modo più efficiente delle variabili.
Inoltre, alcune operazioni su costanti vengono pre-calcolate in fase di compilazione.
 
Se invece di avere una funzione avevi già un template di funzione, basta aggiungere un ulteriore parametro a tale template.
Line 70 ⟶ 65:
class Base {
public:
virtual void f() = 0;
void g() { f(); }
private:
virtual void f() = 0;
};
</source>
 
In questa classe, la funzione <code>g</code> esegue un algoritmo che usachiama unala chiamata afunzione <code>f</code> come puntooperazione diastratta personalizzazioneper delll'algoritmo.
Nella terminologia dei ''design pattern'', <code>g</code> è un [[w:Template_method|template method]], ossia un algoritmo astratto con uno o più punti di personalizzazione.
Lo scopo di questa classe è consentire di scrivere il seguente codice applicativo:
 
<source lang=cpp>
class Derivata1: public Base {
private:
public:
virtual void f() { ... }
};
Line 93 ⟶ 90:
template <class Derivata> class Base {
public:
void f() { static_cast<Derivata*>(this)->f(); }
void g() { f(); }
private:
void f() { static_cast<Derivata*>(this)->f(); }
};
</source>
Line 110 ⟶ 108:
</source>
 
In tal modo, si ha binding statico dellaalla funzione membro <code>Derivata1::f</code> alladella chiamata a <code>f</code> fatta all'interno della funzione membro <code>Base<Derivata1>::g</code>, cioè la chiamata a tale funzione non è più di tipo <code>virtual</code>, e può essere espansa ''inline''.
 
Tuttavia, consupponiamo questa soluzione, seche si volesse aggiungere la seguente definizione:
 
<source lang=cpp>
Line 121 ⟶ 119:
</source>
 
Con questa soluzione non risulterebbesarebbe più possibile definire un puntatore o riferimento a una classe base comune sia a <code>Derivata1</code> eche a <code>Derivata2</code>, in quanto tali classi risultano due tipi senza alcuna relazione; di conseguenza, questa soluzione non è adattaapplicabile se si vuole permettere al codice applicativo di definire un contenitore di oggetti arbitrari derivati dadalla classe Base.
 
Altre limitazioni sono le seguenti:
Line 130 ⟶ 128:
=== Il pattern ''Strategy'' ===
 
'''Se un oggetto che implementa il ''design pattern'' [[w:Strategy pattern|''Strategy'']] (noto anche come design pattern ''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'':
Line 162 ⟶ 160:
MyStrategy s; // Oggetto che rappresenta la mia strategia.
C c; // Oggetto contenente un algoritmo con strategia personalizzabile.
c.set_strategy(s); // Assegnazione della strategia personalizzata.
c.f(); // Esecuzione dell'algoritmo con la strategia assegnata.
</source>
 
Line 188 ⟶ 186:
...
 
C<MyStrategy> c; // Oggetto con strategia preassegnataassegnata staticamente.
c.f(); // Esecuzione con strategia preassegnataassegnata staticamente.
</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 <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.
Inoltre, il codice dell'algoritmo astratto viene duplicato ogni volta che viene personalizzato.
 
=== Operatori bit-a-bit ===