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

Contenuto cancellato Contenuto aggiunto
Ramac (discussione | contributi)
m ha spostato C++ ottimizzato/Ottimizzazione del codice C++ a Ottimizzare C++/Ottimizzazione del codice C++: correggo il titolo
Ramac (discussione | contributi)
m sistemato
Riga 30:
 
In particolare, aggiungi alla classe una funzione membro ''public'' con la seguente firma:
<source lang=cpp>void swap(T&) throw();</source>
e nello stesso namespace che contiene la classe T aggiungi la seguente funzione non-membro:
<source lang=cpp>void swap(T& lhs, T& rhs) { lhs.swap(rhs); }</source>
e, se la classe non è un template di classe, nello stesso file in cui è dichiarata la classe T aggiungi la seguente funzione non-membro:
<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.
Riga 56:
Per consentire di trovare la funzione ''std::swap'', la funzione deve iniziare con la seguente istruzione:
 
<source lang=cpp>using std::swap;</source>
 
== 5.2. Come ridurre i costrutti che richiamano routine del supporto run-time ==
Riga 88:
La suddetta formula è equivalente a quella più intuitiva:
 
<source lang=cpp>min_i <= i && i <= max_i</source>
 
la quale tuttavia richiede due confronti invece di uno solo. In particolare, per controllare che un numero “i” sia valido come indice per accedere a un array di “size” elementi, si può scrivere l’espressione:
 
<source lang=cpp>unsigned(i) < unsigned(size)</source>
 
Se le espressioni sono già unsigned, le conversioni non sono necessarie.
Riga 104:
Supponi che stai scrivendo il seguente template di funzione di libreria, in cui sia x che y non hanno un valore definito in fase di sviluppo della libreria:
 
<source lang=cpp>template <typename T> T f1(T x, T y) { return x * y; }</source>
 
Tale funzione può essere chiamata dal seguente codice applicativo, nel quale x non ha un valore costante, ma y è la costante 4:
 
<source lang=cpp>int a = f1(b, 4);</source>
 
Tale chiamata istanzia automaticamente la seguente funzione:
 
<source lang=cpp>int f1(int x, int y) { return x * y; }</source>
 
Se mentre scrivi la libreria sai che il chiamante ti passerà sicuramente una costante intera come argomento y, puoi trasformare il template nel seguente:
 
<source lang=cpp>template <typename T, int Y> T f2(T x) { return x * Y; }</source>
 
Tale funzione può essere chiamata dal seguente codice applicativo:
 
<source lang=cpp>int a = f2<int, 4>(b);</source>
 
Tale chiamata istanzia automaticamente la seguente funzione:
 
<source lang=cpp>int f2(int x) { return x * 4; }</source>
 
Questa funzione è più veloce della precedente istanza di f1, per i seguenti motivi:
* Viene passato un solo parametro alla funzione (x) invece di due (x e y).
 
* La divisione per una costante intera (4) è più veloce della divisione per una variabile intera (y).
* Viene passato un solo parametro alla funzione (x) invece di due (x e y).
* LaDato divisioneche peril unavalore costante intera (4) è piùuna velocepotenza delladi divisionedue, peril compilatore invece di eseguire una variabilemoltiplicazione intera (y)esegue uno scorrimento di bit a sinistra.
* Dato che il valore costante (4) è una potenza di due, il compilatore invece di eseguire una moltiplicazione intera esegue uno scorrimento di bit a sinistra.
 
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 calcolate in fase di compilazione.
Line 137 ⟶ 136:
 
Supponi che stai scrivendo la seguente classe base di libreria:
<source lang=cpp>
class Base {
class Base {
public:
public:
virtual void f() = 0;
virtual void gf() {= f()0; }
void g() { f(); }
};
};
 
</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 {
class Derivata1: public Base {
public:
public:
virtual void f() { ... }
virtual void f() { ... }
};
};
...
...
Base* p1 = new Derivata1;
Base* p1 = new Derivata1;
p1->g();
p1->g();
 
</source>
In tal caso, è possibile convertire il precedente codice di libreria nel seguente:
<source lang=cpp>
template <class Derivata> class Base {
public:
Line 158 ⟶ 160:
void g() { f(); }
};
</source>
 
Il codice applicativo, di conseguenza, diventerà il seguente:
<source lang=cpp>
class Derivata1: public Base<Derivata1> {
public:
Line 167 ⟶ 170:
Derivata1* p1 = new Derivata1;
p1->g();
</source>
 
In tal modo, si ha binding statico della funzione membro Derivata1::f alla funzione membro Base<Derivata1>::g, cioè la chiamata a tale funzione non è di tipo ''virtual''.
 
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 Derivata1 e Derivata2, 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.
Line 186 ⟶ 191:
 
Supponi che stai scrivendo il seguente codice di libreria, che implementa il pattern strategy,
<source lang=cpp>
 
class C;
class Strategy {
Line 201 ⟶ 206:
Strategy s_;
};
</source>
 
avente lo scopo di consentire il seguente codice applicativo:
<source lang=cpp>
 
class MyStrategy: public Strategy {
public:
Line 215 ⟶ 220:
c.set_strategy(s); // Assegnazione della strategia.
c.f(); // Esecuzione con strategia assegnata.
</source>
 
In tal caso, è possibile convertire il precedente codice di libreria nel seguente:
<source lang=cpp>
 
template <class Strategy>
 
Line 236 ⟶ 241:
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 MyStrategy::is_valid e MyStrategy::run, cioè si evitano chiamate a funzioni virtuali.
 
Line 267 ⟶ 272:
 
Tuttavia, tale tecnica non è “exception-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-safe”, che tuttavia non avrà prestazioni ottimali. La tecnica di assegnamento “exception-safe” più elegante è quella detta “copy&swap”, esemplificata dal seguente codice:
<source lang=cpp>
 
C& C::operator=(C new_value) {
swap(new_value);
return *this;
}
</source>
 
=== 5.4.4. Definisci funzioni in overload per i tipi di argomento più comune, per evitare conversioni. ===
 
Line 288 ⟶ 293:
 
Invece del seguente codice, in cui c e d sono espressioni costanti e b è un’espressione booleana:
<source lang=cpp>a = b ? c : d;</source>
scrivi il seguente codice:
<source lang=cpp>static const tipo lookup_table[] = { d, c };
a = lookup_table[b];</source>
 
L'espressione condizionale viene compilato in un salto condizionato. Se tale salto non è predetto bene, costa di più della lookup-table.
Line 297 ⟶ 302:
Ovviamente questa tecnica può essere estesa a cascate di espressioni condizionali. Per esempio, invece del seguente codice:
 
<source lang=cpp>a = b1 ? c : b2 ? d : b3 ? e : f;</source>
 
scrivi il seguente
<source lang=cpp>
 
static const tipo lookup_table[] = { e, f, d, d, c, c, c, c };
a = lookup_table[b1 * 4 + b2 * 2 + b3];
</source>
 
=== 5.5.2. Cerca di calcolare il valore di un puntatore un po’ prima di quando devi accedere all’oggetto puntato. ===
 
Per esempio, in un loop la seguente istruzione:
<source lang=cpp>
 
a = *++p;
</source>
 
è meno efficiente della seguente:
<source lang=cpp>
 
a = *p++;
</source>
 
in quanto nel primo caso il valore del puntatore è calcolato appena prima di accedere all’oggetto puntato, mentre nel secondo caso è calcolato nell’iterazione precedente. In un processore con pipeline, nel secondo caso si può eseguire contemporaneamente l'accesso all'oggetto puntato da p e l'incremento di p.
 
Line 345 ⟶ 350:
 
Per esempio usando seguente struttura
<source lang=cpp>
 
struct {
char c[400];
Line 351 ⟶ 356:
int i;
};
</source>
 
per indirizzare i campi d e i usando un puntatore all'inizio della struttura, si deve usare un offset di almeno 400 byte.
 
Invece, usando la seguente struttura contenente gli stessi campi in un altro ordine
<source lang=cpp>
 
struct {
double d;
Line 361 ⟶ 366:
char c[400];
};
</source>
 
per indirizzare i campi d e i usando un puntatore all'inizio della struttura, si può usare un offset di pochi byte, che permette l'uso di istruzioni più compatte.
 
Line 367 ⟶ 372:
 
Per esempio, la seguente struttura
<source lang=cpp>
 
struct {
bool b;
Line 374 ⟶ 379:
int i;
};
</source>
 
tipicamente occupa 1 (bool) + 7 (padding) + 8 (double) + 2 (short) + 2 (padding) + 4 (int) = 24 byte.
 
Invece, la seguente struttura contenente gli stessi campi in un altro ordine
<source lang=cpp>
 
struct {
double d;
Line 385 ⟶ 390:
bool b;
};
</source>
 
tipicamente occupa 8 (double) + 4 (int) + 2 (short) + 1 (bool) + 1 (padding) = 16 byte.
 
Line 391 ⟶ 396:
 
Il linguaggio C++non fornisce una primitiva per arrotondare numeri a virgola mobile. La tecnica più semplice per convertire un numero a virgola mobile x all’intero più vicino n, è la seguente espressione:
<source lang=cpp>
 
n = int(floor(x + 0.5f));
</source>
 
Se x è esattamente equidistante tra due interi, n sarà l’intero superiore (0.5 genera 1, 1.5 genera 2, -0.5 genera 0, -1.5 genera -1).
 
Purtroppo, sui processori Pentium e compatibili, tale espressione viene compilata in un codice molto lento. Usando l’istruzione fistp, si ottiene del codice molto più veloce, anche se non esattamente equivalente:
<source lang=cpp>
 
#if defined(__unix__) || defined(__GNUC__)
// Per a Linux 32-bit, con sintassi Gnu/AT&T
Line 406 ⟶ 411:
__asm fistp dword ptr n;
#endif
</source>
 
Il codice precedente arrotonda x all’intero più vicino, ma se x è esattamente equidistante tra due interi, n sarà l’intero pari più vicino (0.5 genera 0, 1.5 genera 2, -0.5 genera 0, -1.5 genera -2). Se questo risultato è tollerabile o addirittura desiderato, allora questo codice è consigliabile. Ovviamente non è portabile su altre famiglie di processori.
 
Line 416 ⟶ 421:
 
Per le operazioni più comuni non si sono vantaggi, ma alcune operazioni particolari possono risultare più veloci. Una di tali operazioni è la moltiplicazione o la divisione per una potenza di due. A tale scopo, basta aggiungere l’esponente di tale potenza all’esponente del numero a virgola mobile. Per esempio, data una variabile “f” di tipo float conforme al formato IEEE 754, e un’espressione intera positiva “n”, invece della seguente istruzione:
<source lang=cpp>
 
f *= pow(2, n);
</source>
 
si può usare la seguente:
<source lang=cpp>
 
if (*(int*)&f & 0x7FFFFFFF) { // se f==0 non fare niente
*(int*)&f += n << 23; // aggiungi n all’esponente
}
</source>
 
=== 5.7.5. Assicurati che la dimensione (sizeof) delle celle piccole o medie degli array sia una potenza di due, e che la dimensione delle celle grandi degli array non sia una potenza di due. ===
 
Line 450 ⟶ 455:
 
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. 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 che è sicuramente positivo o nullo, ne velocizzi la divisione se usi le seguenti espressioni equivalenti: unsigned(s) / c e unsigned(s) % c.
 
[[Categoria:Ottimizzare C++|Ottimizzazione]]