Ottimizzare C++/Scrivere codice C++ efficiente: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 1:
In questa sezione vengono proposte linee-guida per la programmazione in C++ finalizzate a evitare operazioni banalmente inefficienti, senza con questo rendere il codice meno manutenibile.
 
Tali linee-guida potrebbero non dare alcun vantaggio prestazionale, ma molto probabilmente non danno neanche svantaggi, e quindi le si può applicare senza preoccuparsi del loro impatto.
Si consiglia di abituarsi ad adottare sempre tali linee-guida, fin dalla prima stesura, anche nel codice che non ha particolari requisiti di efficienza.
 
==# Come[[Ottimizzare approfittareC++/Scrivere dei costrutticodice C++ efficiente/Costrutti che migliorano le prestazioni|Costrutti ==che migliorano le prestazioni]]
# [[Ottimizzare C++/Scrivere codice C++ efficiente/Costrutti che peggiorano le prestazioni|Costrutti che peggiorano le prestazioni]]
 
# [[Ottimizzare C++/Scrivere codice C++ efficiente/Costruzioni e distruzioni|Costruzioni e distruzioni]]
=== Operatore <code>delete</code> con puntatori nulli ===
# [[Ottimizzare C++/Scrivere codice C++ efficiente/Allocazioni e deallocazioni|Allocazioni e deallocazioni]]
 
# [[Ottimizzare C++/Scrivere codice C++ efficiente/Accesso alla memoria|Accesso alla memoria]]
'''Non verificare che un puntatore sia non-nullo prima di chiamare <code>delete</code> su di esso.'''
# [[Ottimizzare C++/Scrivere codice C++ efficiente/Uso dei thread|Uso dei thread]]
 
Tale controllo viene già fatto da ogni implementazione di <code>delete</code> conforme allo standard, e quindi sarebbe ridondante.
 
=== Funzioni <code>qsort</code> e <code>bsearch</code> ===
 
'''Invece delle funzioni della libreria standard del C <code>qsort</code> e ''bsearch'', usa le funzioni della libreria standard del C++ <code>std:sort</code> e <code>std:lower_bound</code>.'''
 
Le prime due funzioni richiedono necessariamente un puntatore a funzione, mentre le seconde possono usare un oggetto-funzione (o un'espressione lambda).
I puntatori a funzione spesso non sono espansi inline e sono quindi meno efficienti degli oggetti-funzione.
 
=== Funzioni membro <code>const</code> ===
 
'''Dichiara <code>const</code> ogni funzione membro che non modifica lo stato dell'oggetto a cui è applicata.'''
 
Ogni funzione membro riceve l'argomento implicito <code>this</code>.
Indicare <code>const</code> una funzione membro equivale a specificare tale argomento implicito come puntatore a <code>const</code>.
 
La linea-guida 3.3.5 consiglia di usare lo specificatore <code>const</code> negli argomenti di funzione.
 
=== Rappresentazione di simboli ===
 
'''Per rappresentare dei simboli interni, usa degli enumerati, invece di stringhe.'''
 
Un enumerato è implementato come un intero.
Tipicamente, rispetto ad un intero, una stringa occupa più spazio, ed è più lenta da copiare e da confrontare.
 
=== Istruzioni <code>if</code> e <code>switch</code> ===
 
Se devi confrontare un valore intero con una serie di valori costanti, invece di una serie di istruzioni <code>if</code>, usa un'istruzione <code>switch</code>.
 
I compilatori possono sfruttare la regolarità di tale istruzione per applicare alcune ottimizzazioni, in particolare se viene applicata la linea-guida 3.1.11.
 
=== Collezioni incapsulate ===
 
'''Incapsula in apposite classi le collezioni accessibili da più unità di compilazione, in modo da garantire l'intercambiabilità di implementazione.'''
 
In fase di progettazione è difficile decidere quale struttura dati avrà prestazioni ottimali nell'uso effettivo dell'applicazione.
In fase di ottimizzazione ci si può accorgere che cambiando il tipo di un contenitore, per esempio passando da <code>std::vector</code> a <code>std::list</code>, si ottengono prestazioni migliori.
Tuttavia, tale modifica comporta la modifica di gran parte del codice sorgente che utilizza direttamente il contenitore che ha cambiato di tipo.
 
Se una collezione è accessibile da una sola unità di compilazione, tale modifica avrà impatto solamente sul codice sorgente contenuto in tale file, e quindi non è necessario incapsulare tale collezione.
Se invece la collezione è accessibile direttamente da altre unità di compilazione, in futuro ci potrà essere una quantità enorme di codice che dovrà essere modificata a fronte di un cambio di contenitore, e quindi per rendere fattibile in tempi ragionevoli tale ottimizzazione si deve incapsulare il contenitore in una classe la ci interfaccia non cambia al cambiare dell'implementazione del contenitore.
 
I contenitori STL attuano già parzialmente questo principio, ma alcune operazioni sono disponibili solo per alcuni contenitori, e altre hanno comportamento diverso al variare del contenitore.
 
=== Uso dei contenitori STL ===
 
'''Nell'uso dei contenitori STL, a parità di prestazioni, fa' in modo di rendere intercambiabile il contenitore.'''
 
Per esempio, chiama <code>a.empty()</code> invece di <code>a.size() == 0</code>, e chiama <code>iter != a.end()</code> invece di <code>iter < a.end()</code>.
 
Purtroppo, non è sempre possibile scrivere del codice egualmente efficiente e valido per ogni tipo di contenitore.
Tuttavia, riducendo il numero di istruzioni dipendenti dal tipo di contenitore, se in fase di ottimizzazione si dovesse sostituire il contenitore con un altro, si dovrà modificare meno codice.
 
=== Empressioni lambda ===
 
'''Invece di scrivere un ciclo <code>for</code> su un contenitore STL, usa un algoritmo di STL con un'espressione lambda (usando Boost o C++0x).'''
 
Gli algoritmi di STL sono già dei cicli ottimizzati per gli specifici contenitori, ed evitano il rischio di introdurre operazioni inefficienti.
 
Le espressioni lambda sono implementate come oggetti-funzione espansi inline, e quindi sono altrettanto efficienti quanto il codice scritto sul posto.
 
Senza avere a disposizione la funzionalità lambda, nella maggior parte dei casi risulta così scomodo usare gli algoritmi STL che non ne vale la pena.
 
=== Scelta del contenitore di default ===
 
'''Se devi scegliere un contenitore a lunghezza variabile, e sei incerto su quale contenitore scegliere, usa un <code>vector</code>.'''
 
Per insiemi fino a 8 elementi, il <code>vector</code> è il contenitore a lunghezza variabile più efficiente per qualunque operazione.
 
Per insiemi più grandi, altri contenitori possono diventare gradualmente più efficienti a seconda delle operazioni, ma il <code>vector</code> rimane quello che ha minore occupazione di memoria (purché non ci sia capacità in eccesso), minor tempo di scansione completa, e maggiore località di riferimento.
 
=== Funzioni espanse <code>inline</code> ===
 
'''Se usi compilatori che consentono l'ottimizzazione dell'intero programma e l'espansione <code>inline</code> di ogni funzione che il compilatore ritenga opportuno, usa tali opzioni e non dichiarare <code>inline</code> nessuna funzione. Se tali funzionalità del compilatore non fossero disponibili, dichiara </code>inline</code> nei file di intestazione solo le funzioni che contengono non più di tre righe di codice tra le quali non ci sono cicli.'''
 
Le funzioni espanse </code>inline</code> non hanno il costo della chiamata di funzione, che è tanto più grande quanti più sono gli argomenti della funzione. Inoltre, dato che il codice è consecutivo con quello del chiamante, hanno migliore località di riferimento. Infine, dato che il codice intermedio delle funzioni espanse </code>inline</code> si fonde con quello del chiamante, tale codice può essere facilmente ottimizzato dal compilatore.
 
Le funzioni molto piccole, cioè un semplice assegnamento o una semplice istruzione <code>return</code>, quando espanse <code>inline</code> comportano addirittura una riduzione del codice generato complessivo.
 
Tuttavia, ogni volta che viene espansa <code>inline</code> una routine che contiene una quantità notevole di codice, si duplica tale codice, e quindi si ingrandisce la dimensione complessiva del programma, con conseguente rallentamento a causa della minore località di riferimento.
 
Tra le routine non piccolissime, solo quelle critiche per la velocità verranno rese ''inline'' in fase di ottimizzazione.
 
=== Valori dei casi di istruzioni <code>switch</code> ===
 
'''Come costanti per i casi delle istruzioni <code>switch</code>, usa sequenze compatte di valori, cioè senza lacune o con poche lacune.'''
 
I compilatori ottimizzanti, quando compilano un'istruzione <code>switch</code> i cui valori dei casi comprendono la maggior parte dei valori interi compresi in un intervallo, invece di generare una sequenza di istruzioni <code>if</code>, generano una ''jump-table'', ossia un array degli indirizzi in cui inizia il codice di ogni caso, e quando eseguono lo <code>switch</code> usano tale tabella per saltare al codice associato al caso.
 
Per esempio, il seguente codice C++:
 
<source lang=cpp>
switch (i) {
case 10:
case 13:
funz_a();
break;
case 11:
funz_b();
break;
}
</source>
 
probabilmente genera del codice macchina corrispondente al seguente pseudo-codice:
 
<source lang=cpp>
// Questo non è codice C++
static indirizzo a[] = { caso_a, caso_b, 0, caso_a };
unsigned int indice = i - 10;
if (indice <= 3) goto a[indice];
goto fine:
caso_a: funz_a(); goto fine;
caso_b: funz_b(); goto fine;
fine:
</source>
 
Invece, il seguente codice C++:
 
<source lang=cpp>
switch (i) {
case 100:
case 130:
funz_a();
break;
case 110:
funz_b();
break;
}
</source>
 
probabilmente genera del codice macchina corrispondente al seguente codice:
 
<source lang=cpp>
if (i == 100) goto caso_a;
if (i == 130) goto caso_a;
if (i == 110) goto caso_b;
goto fine;
caso_a: funz_a(); goto fine;
caso_b: funz_b(); goto fine;
fine:
</source>
 
Per così pochi casi, probabilmente non ci sono molte differenze, ma con l'aumentare del numero di casi la primo codice è più efficiente in quanto esegue un solo ''goto calcolato'' invece di una cascata di <code>if</code>.
 
=== Ordine dei casi dell'istruzione <code>switch</code> ===
 
'''Nelle istruzioni <code>switch</code>, poni prima i casi più tipici.'''
 
Se il compilatore non generasse la ''jump-table'', i casi verrebbero confrontati in ordine di comparizione, per cui nei casi più tipici verrebbero fatti meno confronti.
 
=== Raggruppamento di più array in un array di strutture ===
 
'''Invece di elaborare in parallelo due o più array della stessa lunghezza, elabora un solo array di oggetti compositi.'''
 
Esempio: Invece del seguente codice:
 
<source lang=cpp>
const int n = 10000;
double a[n], b[n], c[n];
for (int i = 0; i < n; ++i) {
a[i] = b[i] + c[i];
}
</source>
 
scrivi il seguente codice:
 
<source lang=cpp>
const int n = 10000;
struct { double a, b, c; } s[n];
for (int i = 0; i < n; ++i) {
s[i].a = s[i].b + s[i].c;
}
</source>
 
In tal modo, i dati da elaborare insieme sono più vicini tra di loro in memoria, e questo permette di ottimizzare l'uso della cache dei dati, e di indirizzare tali dati con istruzioni più compatte, che quindi ottimizzano l'uso della cache del codice.
 
=== Raggruppamento di argomenti di funzione ===
 
'''Se una funzione riceve almeno sei argomenti, e viene chiamata spesso con gli stessi valori tranne eventualmente uno o due, crea un oggetto che contiene tutti gli argomenti, e passa alla funzione tale oggetto per puntatore a costante o riferimento a costante.'''
 
Se una funzione riceve pochi argomenti, questi vengono posti direttamente nei registri e quindi il passaggio è molto veloce; ma se non sono pochi, gli argomenti devono essere posti nello stack, anche se sono gli stessi della precedente chiamata alla stessa funzione.
 
Se invece si passa solo l'indirizzo di una struttura, questo indirizzo viene sicuramente posto in un registro, e i campi della struttura che non sono modificati tra chiamate successive della funzione devono essere assegnati solo la prima volta.
 
Per esempio, rispetto al seguente codice:
 
<source lang=cpp>
for (int i = 0; i < 1000; ++i) {
f(i, a1, a2, a3, a4, a5, a6, a7, a8);
}
</source>
 
il seguente codice è probabilmente più efficiente:
 
<source lang=cpp>
struct {
int i;
tipo a1, a2, a3, a4, a5, a6, a7, a8;
} s;
s.a1 = a1; s.a2 = a2; s.a3 = a3; s.a4 = a4;
s.a5 = a5; s.a6 = a6; s.a7 = a7; s.a8 = a8;
for (int i = 0; i < 1000; ++i) {
s.i = i;
f(s);
}
</source>
 
=== Uso dei tipi più efficienti ===
 
'''Per memorizzare in un oggetto dei numeri interi, usa il tipo <code>int</code> o il tipo <code>unsigned int</code>, a meno che sia necessario un tipo più lungo; per memorizzare dei caratteri usa il tipo <code>char</code>, a meno che serva il tipo <code>wchar_t</code>; e per memorizzare dei numeri a virgola mobile, usa il tipo <code>double</code>, a meno che sia necessario il tipo <code>long double</code>. Se l'oggetto risultante è medio o grande, sostituisci i tipi interi con il tipo intero più piccolo in grado di contenerlo, ma senza usare i ''bit-field'', e sostituisci i tipi a virgola mobile con il tipo <code>float</code>, a meno che sia necessaria maggiore precisione.'''
 
I tipi <code>int</code> e <code>unsigned int</code> sono per definizione quelli più efficienti su qualunque piattaforma.
 
I tipi <code>double</code> sono efficienti quanto i <code>float</code>, ma sono più precisi.
 
Alcuni tipi di processore elaborano più velocemente gli oggetti di tipo <code>signed char</code>, mentre altre elaborano più velocemente gli oggetti di tipo <code>unsigned char</code>.
Pertanto, in C e in C++ esiste il tipo <code>char</code> che corrisponde al tipo di carattere elaborato più velocemente sul processore target.
 
Il tipo <code>char</code> può contenere solo piccoli insiemi di caratteri; tipicamente fino a un massimo di 255 caratteri distinti.
Per memorizzare set di caratteri più grandi, si deve ricorrere al tipo <code>wchar_t</code>, che ovviamente è meno efficiente.
 
Nel caso di interi contenuti in array medi o grandi, o in collezioni che si presume saranno tipicamente medie o grandi, è meglio minimizzare la dimensione in byte della collezione.
Questo si può fare sostituendo gli <code>int</code> con <code>short</code> o <code>signed char</code>, sostituendo gli <code>unsigned int</code> con <code>unsigned short</code> o <code>unsigned char</code>, e sostituendo i <code>double</code> con i <code>float</code>.
Per esempio, per memorizzare un numero intero che può essere compreso tra 0 e 1000, si può usare un <code>unsigned short</code>, mentre per memorizzare un numero intero che può essere compreso tra -100 e 100, si può usare un <code>signed char</code>.
 
I bit-field contribuirebbero a minimizzare la dimensione in byte dell'array, ma la loro elaborazione introduce un rallentamento che potrebbe essere eccessivo, per cui andrebbero eventualmente introdotti solo in fase di ottimizzazione.
 
=== Uso di funzioni membro di contenitori ===
 
'''Per cercare un elemento in un contenitore, usa una funzione membro del contenitore, invece di un algoritmo STL.'''
 
Se è stata creata una tale funzione membro specifica quando esisteva già un algoritmo STL generico, è solo perché tale funzione membro è più efficiente.
 
Per esempio, per cercare in un oggetto <code>std::set</code> si può usare l'algoritmo generico <code>find</code>, o la funzione membro <code>find</code>; ma il primo ha complessità lineare (O(n)), mentre la seconda ha complessità logaritmica (O(log(n))).
 
=== Ricerca in sequenze ordinate ===
 
'''Per cercare un elemento in una sequenza ordinata, usa gli algoritmi <code>lower_bound</code>, <code>upper_bound</code>, <code>equal_range</code>, o <code>binary_search</code>.'''
 
Dato che tutti i citati algoritmi usano la ricerca binaria di complessità logaritmica (O(log(n))), sono più veloci dell'algoritmo <code>find</code>, che usa la ricerca sequenziale di complessità lineare (O(n)).
 
== Come evitare i costi di costrutti C++ che peggiorano le prestazioni ==
 
Rispetto al linguaggio C, il linguaggio C++ aggiunge alcuni costrutti, il cui utilizzo peggiora l'efficienza.
 
Alcuni di tali costrutti sono piuttosto efficienti, ed è quindi del tutto ragionevole pagarne il costo quando servono, ma è altrettanto ragionevole non pagarne il costo quando non li si usa.
 
Altri costrutti sono alquanto inefficienti, e devono quindi essere usati con grande parsimonia.
 
=== L'operatore <code>throw</code> ===
 
'''Chiama l'operatore <code>throw</code> solamente quando si dovrà avvisare l'utente del fallimento del comando corrente.'''
 
Il sollevamento di una eccezione ha un costo molto elevato, dell'ordine dei 10000 cicli di processore.
Se tale operazione viene effettuata solamente ogni volta che un messaggio viene mostrato all'utente o scritto in un file di log, si ha la garanzia che non verrà eseguita troppo spesso.
Se invece la si effettua come operazione algoritmica, anche se pensata inizialmente per essere eseguita raramente, potrebbe finire per essere eseguita frequentemente.
 
=== Derivazione <code>virtual</code> ===
 
'''Non usare sistematicamente la derivazione <code>virtual</code>, ma solo quando due o più classi devono condividere la rappresentazione di una classe base comune.'''
 
Le funzioni membro delle classi base derivate in modo <code>virtual</code> sono un po' più lente delle funzioni membro derivate in modo non-<code>virtual</code>.
 
Per esempio, considera le seguenti definizioni di classe:
<source lang=cpp>
class A { ... };
class B1: public A { ... };
class B2: public A { ... };
class C: public B1, public B2 { ... };
</source>
 
Con tali definizioni, ogni oggetto di classe C contiene due oggetti distinti di classe A, uno facente parte della classe base B1, e l'altro facente parte della classe base B2.
 
Questo non costituisce un problema se la classe A non ha nessuna variabile membro non-<code>static</code>.
 
Se invece tale oggetto di classe A contiene qualche variabile membro, e si intende che debba essere unico per ogni oggetto di classe C, si deve usare la derivazione <code>virtual</code>, nel seguente modo:
 
<source lang=cpp>
class A { ... };
class B1: virtual public A { ... };
class B2: virtual public A { ... };
class C: public B1, public B2 { ... };
</source>
 
Questa situazione è l'unica in cui è necessaria la derivazione <code>virtual</code>.
 
=== Funzioni membro <code>virtual</code> ===
 
'''In ogni classe, non definire sistematicamente <code>virtual</code> tutte le funzioni membro, ma solo le funzioni membro di cui prevedi la necessità di una ridefinizione, a parte il distruttore, il quale deve essere definito <code>virtual</code> se e solo se la classe contiene almeno un'altra funzione membro <code>virtual</code>.'''
 
A parità di condizioni, le classi che contengono almeno una funzione membro <code>virtual</code> occupano un po' più spazio delle classi che non ne contengono, e gli oggetti delle classi che contengono almeno una funzione membro <code>virtual</code> occupano un po' più spazio e la loro costruzione richiede un po' più di tempo rispetto agli oggetti di classi che non contengono funzioni membro <code>virtual</code>.
 
Le funzioni membro <code>virtual</code> occupano po' più spazio e sono un po' più lente da chiamare delle funzioni membro non-<code>virtual</code>.
 
=== Funzioni membro <code>static</code> ===
 
'''In ogni classe, dichiara <code>static</code> ogni funzione membro che non accede ai membri non-<code>static</code> di tale classe.'''
 
In altre parole, dichiara <code>static</code> tutte le funzioni membro che puoi.
 
In questo modo, non viene passato l'argomento implicito <code>this</code>.
 
=== Template di classi polimorfiche ===
 
'''Non definire template di classi polimorfiche.'''
 
I template di classe, ogni volta che vengono istanziati, producono una copia del codice oggetto, e se contengono funzioni virtuali producono una copia della ''vtable'' e della ''RTTI''. Questi dati ingrandiscono eccessivamente il programma.
 
=== Annullamento dell'argomento di <code>delete</code> ===
 
'''Non annullare un puntatore dopo aver chiamato <code>delete</code> su di esso, se sei sicuro che tale puntatore non verrà più usato.'''
 
L'uso più tipico dell'operatore <code>delete</code> si ha quando in un distruttore si dealloca un oggetto posseduto dall'oggetto in corso di distruzione; in tale contesto, si chiama <code>delete</code> su una variabile membro di tipo puntatore.
 
Dato che solitamente un distruttore non è così complicato da faticare a capire quali parti dell'oggetto corrente sono già state distrutte e quali non ancora, e dato che il puntatore usato per la chiamata a <code>delete</code> cesserà di esistere alla fine del distruttore stesso, anche se si lascia il valore corrente del puntatore non più valido, non si corre il rischio di usarlo accidentalmente.
 
D'altra parte, annullare il puntatore richiede una seppur piccolissima quantità di tempo.
 
Come tecnica di collaudo o di debugging, si può adottare la regola di annullare sempre il puntatore a cui è stato applicato l'operatore <code>delete</code> nella generazione di una versione finalizzata al collaudo o debugging, ma farlo anche nella generazione del codice di produzione è un'inutile inefficienza.
 
=== Uso di deallocatori automatici ===
 
'''Non usare una libreria di garbage-collection né gli smart-pointer con reference-count (boost::shared_ptr), a meno che se ne dimostri l’opportunità per il caso specifico.'''
 
La garbage collection, cioè il recupero automatico della memoria non più referenziata, fornisce la comodità di non doversi occupare della deallocazione della memoria, e previene i ''memory leak''. Tale funzionalità non viene fornita dalla libreria standard, ma viene fornita da librerie non-standard.
Tuttavia, tale tecnica di gestione della memoria offre prestazioni peggiori della deallocazione esplicita.
 
La libreria standard del C++98 contiene un solo smart-pointer, l'<code>auto_ptr</code>, che è efficiente. Altri smart-pointer sono forniti da librerie non-standard, come Boost, o verranno forniti dal C++0x. Tra di essi, gli smart-pointer basati su reference-count, come lo <code>shared_ptr</code> di Boost, sono meno efficienti, e quindi devono essere usati sono nei casi in cui se ne dimostra la necessità. In particolare, compilando con l'opzione di gestione del multithreading, tali funzionalità hanno pessime prestazioni, in quanto devono garantire l'atomicità delle operazioni.
 
Normalmente bisognerebbe, in fase di progettazione, cercare di assegnare ogni oggetto ad un proprietario, che avrà la responsabilità di distruggerlo.
Solo quando tale assegnazione è difficile, in quanto più oggetti tendono a rimpallarsi la responsabilità di distruggere un oggetto, risulta opportuno usare uno smart-pointer con reference-count oppure una libreria di garbage-collection.
 
=== Il modificatore <code>volatile</code> ===
 
'''Non definire sistematicamente <code>volatile</code> ogni variabile, ma solo quelle che vengono modificate in modo asincrono da dispositivi hardware o da altri thread.'''
 
L'uso del modificatore <code>volatile</code> impedisce al compilatore di allocare una variabile in un registro.
Questo garantisce che tutti i dispositivi e tutti i thread ''vedano'' la stessa variabile, ma rende molto più lente le operazioni che manipolano tale variabile.
 
== Come evitare costruzioni e distruzioni di oggetti ==
 
=== Ambito delle variabili ===
 
'''Dichiara le variabili il più tardi possibile.'''
 
Dichiarare una variabile il più tardi possibile, significa sia dichiararla nell'ambito più stretto possibile, sia dichiararla il più avanti possibile entro quell'ambito.
Essere nell'ambito più stretto possibile comporta che se tale ambito non viene mai eseguito, l'oggetto associato alla variabile non viene mai costruito né distrutto.
Essere il più avanti possibile all’interno di un ambito comporta che se prima di tale dichiarazione c'è un'uscita prematura, tramite <code>return</code> o <code>break</code> o <code>continue</code>, l'oggetto associato alla variabile non viene mai costruito né distrutto.
 
Inoltre, spesso all'inizio di una routine non si ha un valore appropriato per inizializzare l'oggetto associato alla variabile, e quindi si è costretti a inizializzarla con un valore di default, e poi assegnarle il valore appropriato.
Se invece la si dichiara quando si ha a disposizione il valore appropriato, la si può inizializzare con tale valore senza bisogno di fare un successivo assegnamento.
 
=== Inizializzazioni ===
 
'''Usa inizializzazioni invece di assegnamenti. In particolare, nei costruttori usa le liste di inizializzazione.'''
 
Se un oggetto di classe non viene inizializzato esplicitamente, viene comunque inizializzato automaticamente dal costruttore di default.
In generale, chiamare il costruttore di default seguito da un assegnamento di un valore è meno efficiente o ugualmente efficiente che chiamare solo un costruttore con tale valore.
 
=== Operatori di incremento/decremento ===
 
'''Usa gli operatori prefissi di incremento (<code>++</code>) e decremento (<code>--</code>) invece dei corrispondenti operatori postfissi, se il valore dell'espressione non viene usato.'''
 
Se l'oggetto incrementato è di un tipo fondamentale, non ci sono differenze tra le due forme, ma se si tratta di un tipo composito, l'operatore postfisso comporta la creazione di un inutile oggetto temporaneo, mentre l'operatore prefisso no.
 
Siccome ogni oggetto che è attualmente di un tipo fondamentale potrebbe diventare in futuro di una classe, è bene usare sempre quell'operatore che in quest'ultimo caso è più efficiente.
 
Se invece il valore dell'espressione formata dall’operatore di incremento o decremento viene usata in un'espressione più grande, potrebbe essere opportuno usare l'operatore postfisso.
 
=== Operatori compositi di assegnamento ===
 
'''Usa gli operatori compositi di assegnamento (come in <code>a += b</code>) invece degli operatori semplici combinati con operatori di assegnamento (come in <code>a = a + b</code>).'''
 
Tipicamente un operatore semplice, come nell'espressione ''a + b'', crea un oggetto temporaneo.
Per esempio, nel seguente codice, gli operatori ''+'' creano stringhe temporanee, la cui creazione e distruzione richiede tempo:
 
<source lang=cpp>
string s1("abc");
string s2 = s1 + " " + s1;
</source>
 
Il seguente codice, equivalente al precedente, risulta più efficiente, in quanto l'operatore ''+='' non crea oggetti temporanei:
 
<source lang=cpp>
string s1("abc");
string s2 = s1;
s2 += " ";
s2 += s1;
</source>
 
=== Passaggio di argomenti alle funzioni ===
 
'''Quando devi passare un argomento <code>x</code> di tipo <code>T</code> a una funzione, usa il seguente criterio:'''
Se <code>x</code> è un argomento di solo input,
se <code>x</code> può essere nullo,
passalo per puntatore a costante (<code>const T* x</code>),
altrimenti, se <code>T</code> è un tipo fondamentale o un iteratore o un oggetto-funzione,
passalo per valore (<code>T x</code>) o per valore costante (<code>const T x</code>),
altrimenti,
passalo per riferimento a costante (<code>const T& x</code>),
altrimenti, cioè se <code>x</code> è un argomento di solo output o di input/output,
se <code>x</code> può essere nullo,
passalo per puntatore a non-costante (<code>T* x</code>),
altrimenti,
passalo per riferimento a non-costante (<code>T& x</code>).
 
Il passaggio per riferimento è più efficiente del passaggio per puntatore in quanto facilita al compilatore l’eliminazione della variabile, e in quanto il chiamato non deve verificare se il riferimento è valido o nullo; tuttavia, il puntatore ha il pregio di poter rappresentare un valore nullo, ed è più efficiente passare solo un puntatore, che un riferimento a un oggetto insieme a un booleano che indica se tale riferimento è valido.
 
Per oggetti che possono essere contenuti in uno o due registri, il passaggio per valore è più efficiente o ugualmente efficiente del passaggio per riferimento, in quanto tali oggetti possono essere contenuti in registri e non hanno livelli di indirettezza, pertanto questo è il modo più efficiente di passare oggetti sicuramente piccoli, come i tipi fondamentali, gli iteratori e gli oggetti-funzione.
Per oggetti più grandi di due registri, il passaggio per riferimento è più efficiente del passaggio per valore, in quanto tali oggetti non devono essere copiati.
 
Un oggetto composito veloce da copiare potrebbe essere efficientemente passato per valore, ma, a meno che si tratti di un iteratore o di un oggetto-funzione, per i quali si assume l’efficienza della copia, tale tecnica è rischiosa, in quanto l’oggetto potrebbe diventare in futuro più lento da copiare.
Per esempio, se un oggetto di classe <code>Point</code> contiene solo due <code>float</code>, potrebbe essere efficientemente passato per valore; ma se in futuro si aggiungesse un terzo <code>float</code>, o se i due <code>float</code> diventassero due <code>double</code>, potrebbe diventare più efficiente il passaggio per riferimento.
 
=== Dichiarazione <code>explicit</code> ===
 
'''Dichiara <code>explicit</code> tutti i costruttori che possono ricevere un solo argomento, eccetto i costruttori di copia delle classi concrete.'''
 
I costruttori impliciti possono essere chiamati automaticamente dal compilatore che esegue una conversione automatica. A seconda della complessità del costruttore, tale chiamata può richiedere molto più tempo del necessario. Rendendo obbligatoriamente esplicita tale conversione, il compilatore potrebbe scegliere un'altra funzione in overload, evitando così di chiamare il costruttore, oppure segnalare errore e costringere il programmatore a scegliere un’altra strada per evitare la chiamata al costruttore.
 
Per i costruttori di copia delle classi concrete si deve fare eccezione, per consentirne il passaggio per valore. Per le classi astratte, anche i costruttori di copia possono essere dichiarati <code>explicit</code>, in quanto, per definizione, le classi astratte non si possono istanziare, e quindi gli oggetti di tale tipo non dovrebbero mai essere passati per valore.
 
=== Operatori di conversione ===
 
'''Non dichiarare operatori di conversione, se non per mantenere la compatibilità con una libreria obsoleta (in C++0x, dichiarali explicit).'''
 
Gli operatori di conversioni consentono conversioni implicite, e quindi incorrono nello stesso problema dei costruttori impliciti.
 
Se tali conversioni sono necessarie, fornisci invece una funzione membro equivalente, che può essere chiamata solo esplicitamente.
 
L'unico utilizzo che rimane accettabile per gli operatori di conversione si ha quando si sta convertendo una grande mole di software dall'uso di una libreria all'uso di un'altra libreria simile. Durante la fase di transizione, in cui le due librerie coesistono, può essere comodo avere operatori che convertono automaticamente gli oggetti dai tipi della vecchia libreria ai tipi della nuova libreria.
 
=== Idioma ''Pimpl'' ===
 
'''Non usare sistematicamente l'idioma ''Pimpl'', ma solo quando vuoi rendere il resto del programma indipendente dall'implementazione di una classe.'''
 
L'idioma ''Pimpl'' (che significa Puntatore a IMPLementazione) consiste nel memorizzare nell'oggetto solamente un puntatore alla struttura che contiene tutte le informazioni utili di tale oggetto.
 
Il vantaggio principale di tale idioma è che velocizza la compilazione incrementale del codice, cioè rende meno probabile che una piccola modifica ai sorgenti comporti la necessità di ricompilare grandi quantità di codice.
 
Tale idioma consente anche di velocizzare alcune operazioni, come lo <code>swap</code> tra due oggetti, ma in generale rallenta gli accessi ai dati dell'oggetto a causa del livello di indirettezza, e provoca un'allocazione aggiuntiva per ogni creazione e copia di tale oggetto.
Quindi non dovrebbe essere usato per classi le cui funzioni membro pubbliche sono chiamate frequentemente.
 
=== Iteratori e oggetti-funzione ===
 
'''Nelle classi di iteratori o di oggetti-funzione, fa' in modo che tali oggetti siano piccolissimi e che non allochino memoria dinamica.'''
 
Gli algoritmi di STL passano tali oggetti per valore.
Pertanto, se la loro copia non è estremamente efficiente, gli algoritmi STL diventano lenti.
 
== Come evitare inutili allocazioni e deallocazioni di memoria ==
 
L’allocazione e la deallocazione di memoria dinamica sono operazioni molto lente, se confrontate con l’allocazione e la deallocazione di memoria automatica, cioè su stack.
 
Inoltre, tale tipo di allocazione comporta uno spreco di spazio per ogni allocazione, genera frammentazione della memoria virtuale, e produce una scarsa località dei dati, con conseguente scadente utilizzo sia delle cache dei dati della CPU che della memoria virtuale.
 
Tale allocazione/deallocazione in linguaggio C veniva fatta con le funzioni malloc e free. In C++, pur essendo ancora disponibili tali funzioni, le funzioni normalmente usate a tale scopo sono gli operatori <code>new</code>, <code>new[]</code>, <code>delete</code>, e <code>delete[]</code>.
 
Ovviamente, un modo di ridurre le allocazioni è ridurre il numero di oggetti costruiti, e quindi la sezione “Come evitare inutili costruzioni e le distruzioni di oggetti” serve indirettamente anche allo scopo di questa sezione.
 
Tuttavia, qui si presenteranno regole per ridurre il numero di allocazioni di memoria per un dato numero di chiamate all'operatore new.
 
=== Array di lunghezza fissa ===
 
'''Se un array statico o non grande ha lunghezza costante, non usare un oggetto <code>vector</code>, ma usa un array del C, o un oggetto <code>boost:array</code>.'''
 
I vector memorizzano i dati in un buffer allocato dinamicamente, mentre le altre soluzioni proposte allocano i dati nell'oggetto stesso.
Questo consente di evitare allocazioni/deallocazioni di memoria dinamica e di favorire la località dei dati.
 
Se l'array è medio o grande, tali vantaggi diminuiscono, e invece risulta più importante evitare di usare troppo spazio sullo stack.
 
=== Allocatore a blocchi ===
 
'''Se devi allocare numerosi blocchi di memoria della stessa dimensione, assicurati di usare un allocatore a blocchi.'''
 
Un ''allocatore a blocchi'' (detto anche ''allocatore a pool'') alloca blocchi di memoria medi o grandi, e fornisce servizi di allocazione/deallocazione di blocchi più piccoli di dimensione costante, offrendo alta velocità di allocazione/deallocazione, bassa frammentazione della memoria, uso efficiente delle cache dei dati e della memoria virtuale.
 
In particolare, un allocatore di questo tipo migliora notevolmente le prestazioni dei contenitori <code>std::list</code>, <code>std::set</code>, <code>std::multi_set</code>, <code>std::map</code>, e <code>std::multi_map</code>.
 
Se la tua implementazione della libreria standard non usa già un allocatore a blocchi per questi contenitori, dovresti procurartene uno (per esempio, questo: http://www.codeproject.com/KB/stl/blockallocator.aspx), e specificarlo come parametro di template per le istanze di tali template di contenitori.
 
=== Aggiunta di elementi a collezione ===
 
'''Per aggiungere elementi in fondo a una collezione, usa <code>push_back</code> per aggiungere un singolo elemento, <code>back_inserter</code> per far aggiungere elementi a un algoritmo STL, <code>insert</code> per inserire una sequenza.'''
 
La funzione <code>push_back</code> garantisce un tempo lineare ammortizzato, in quanto, nel caso del <code>vector</code>, ingrandisce esponenzialmente la capacità.
 
La classe <code>back_inserter</code> chiama internamente la funzione <code>push_back</code>.
 
La funzione <code>insert</code> permette di inserire in modo ottimizzato un'intera sequenza, e quindi una chiamata di questo tipo è più veloce di numerose chiamate a <code>push_back</code>.
 
== Come velocizzare l'accesso alla memoria principale ==
 
=== Ordine di accesso alla memoria ===
 
'''Accedi alla memoria in ordine crescente; in particolare, scandisci gli array in ordine crescente, scandisci gli array multidimensionali usando gli indice più a destra per i cicli più interni, nei costruttori delle classi e negli operatori di assegnamento (operator=) accedi alle variabili membro nell’ordine in cui sono dichiarate nella classe.'''
 
La cache dei dati ottimizza gli accessi alla memoria in ordine sequenziale crescente. Quando si itera su un array multidimensionale, il loop più interno deve iterare sull'ultimo indice. In tal modo, è garantito che le celle vengono elaborate nell’ordine in cui si trovano in memoria. Per esempio:
 
<source lang=cpp>
float a[num_livelli][num_righe][num_colonne];
for (int liv = 0; liv < num_livelli; ++ liv) {
for (int r = 0; r < num_righe; ++r) {
for (int c = 0; c < num_colonne; ++c) {
a[liv][r][c] += 1;
}
}
}
</source>
 
=== Allineamento di memoria ===
 
'''Lascia l'allineamento di memoria suggerito dal compilatore.'''
 
I compilatori attivano di default un criterio di allineamento dei tipi fondamentali, per cui le variabili di ogni tipo possono iniziare solo a determinati indirizzi di memoria.
Tale criterio solitamente garantisce le massime prestazioni, ma può introdurre degli spazi inutilizzati tra le variabili.
Se per alcune strutture è necessario eliminare tali spazi, usa le direttiva ''pragma'' per confinare tale impaccamento alle sole strutture per cui è necessario.
 
=== Raggruppamento di funzioni in unità di compilazione ===
 
'''Definisci nella stessa unità di compilazione tutte le funzioni membro di una classe, le funzioni <code>friend</code> di tale classe e le funzioni delle classi <code>friend</code> di tale classe, a meno che il file risultante diventi scomodo da gestire per la sua dimensione eccessiva.'''
 
In tal modo, sia il codice macchina prodotto compilando tali routine sia i dati statici definiti in tali classi e routine avranno indirizzi vicini tra loro; inoltre, così si consente ai compilatori che non effettuano ottimizzazioni sull'intero programma di ottimizzare le chiamate tra tali funzioni.
 
=== Raggruppamento di variabili in unità di compilazione ===
 
'''Definisci le variabili globali nell'unità di compilazione in cui sono usate più spesso.'''
 
In tal modo, tali variabili avranno indirizzi vicini tra loro e vicini a quelli delle variabili statiche definite in tale unità di compilazione; inoltre, così si consente ai compilatori che non effettuano ottimizzazioni sull'intero programma di ottimizzare l'utilizzo di tali variabili dalle funzioni che le usano maggiormente.
 
=== Funzioni e variabili private in unità di compilazione ===
 
'''Dichiara in un namespace anonimo le variabili e le funzioni globali a un'unità di compilazione, ma non usate da altre unità di compilazione.'''
 
In tal modo, si dichiara che non verranno usate da altre unità di compilazione. Questo permette ai compilatori che non effettuano ottimizzazioni sull'intero programma di ottimizzare l'utilizzo di tali variabili e funzioni.
 
== Uso dei thread ==
 
=== Thread di lavoro ===
 
'''Ogni volta che in un'applicazione interattiva devi eseguire un compito che può richiedere più di una manciata di secondi, assegna tale compito a un apposito thread di calcolo di priorità più bassa del normale.'''
 
In tal modo, il thread principale si occupa solo di gestire l'interfaccia utente, ed è pronto a rispondere ad altri comandi.
Assegnando al thread di calcolo priorità più bassa del normale, l'interfaccia utente rimane veloce quasi come se non ci fosse un’elaborazione in corso.
 
Questa linea-guida in realtà non aiuta a migliorare la velocità dell'applicazione, ma solo la sua responsività.
Tuttavia, questo è percepito dagli utenti come un aumento di velocità.
 
=== Thread di lavoro multipli ===
 
'''In un sistema multicore, se riesci a suddividere un’elaborazione in più thread, usa tanti thread di calcolo quanti sono i core di processore.'''
 
In tal modo ogni core può elaborare un thread. Se i thread di calcolo fossero più dei processori, ci sarebbe contesa tra i thread, e questo rallenterebbe l’elaborazione. Il thread di interfaccia utente non rallenta, in quanto è pressoché inattivo.
 
=== Uso di librerie multi-threaded ===
 
'''Se sviluppi un'applicazione single-threaded non usare librerie progettate per applicazioni multi-threaded.'''
 
Le tecniche per rendere thread-safe una libreria possono dover usare memoria e tempo. Se non usi i thread, evita di pagarne il costo.
 
=== Creazione di librerie multi-threaded ===
 
'''Se sviluppi una libreria, gestisci correttamente il caso in cui sia usata da applicazioni multi-threaded, ma ottimizza anche il caso in cui sia usata da applicazioni single-threaded.'''
 
Le tecniche per rendere thread-safe una libreria possono dover usare memoria e tempo. Se gli utenti della tua libreria non usano i thread, evita di fargliene pagare il costo.
 
=== Mutua esclusione ===
 
'''Usa primitive di mutua esclusione solo quando più thread accedono contemporaneamente agli stessi dati, e almeno uno degli accessi è in scrittura.'''
 
Le primitive di mutua esclusione richiedono tempo.
 
Se sei sicuro che un thread inizia a leggere un'area di memoria solo dopo che un altro ha finito di scriverla, non c'è bisogno di sincronizzare ulteriormente gli accessi.
 
Se sei sicuro che in un dato periodo di tempo nessun thread scrive in un'area di memoria, non c'è bisogno di sincronizzare gli accessi in lettura a tale area.
 
[[Categoria:Ottimizzare C++|Scrivere codice C++ efficiente]]