Ottimizzare C++/Scrivere codice C++ efficiente/Costrutti che migliorano le prestazioni: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Riportate modifiche da en.wikibooks
Riportate modifiche da en.wikibooks
Riga 127:
 
'''Per rappresentare dei simboli interni, usa degli enumerati, invece di stringhe.'''
 
Per esempio, rispettoinvece aldel seguente codice:
 
<source lang=cpp>
const char* direzioni[] = { "Nord", "Sud", "Est", "Ovest" };
</source>
 
scrivi il seguente codice:
 
<source lang=cpp>
enum direzioni { Nord, Sud, Est, Ovest };
</source>
 
Un enumerato è implementato come un intero.
Tipicamente, rispetto ad un intero, una stringa occupa più spazio, ed è più lenta da copiare e da confrontare.
Inoltre, tale pratica è più sicura, in quanto se si sbaglia a scrivere la costante, il compilatore si lamenterà di un identificatore non definito, mentre se si passa una stringa errata si crea un errore potenzialmente difficile da rintracciare.
 
=== Istruzioni <code>if</code> e <code>switch</code> ===
Line 136 ⟶ 147:
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>.
 
Per esempio, invece del seguente codice:
I compilatori possono sfruttare la regolarità di tale istruzione per applicare alcune ottimizzazioni, in particolare se viene applicata la linea-guida "Valori dei casi di istruzioni <code>switch</code>" di questa sezione.
 
<source lang=cpp>
=== Espressioni lambda ===
if (a[i] == 1) f();
else if (a[i] == 2) g();
else if (a[i] == 5) h();
</source>
 
scrivi il seguente codice:
'''Invece di scrivere un ciclo <code>for</code> su un contenitore STL, usa un algoritmo di STL con un'espressione lambda (usando la libreria [[http://www.boost.org/ Boost]] o C++0x).'''
 
<source lang=cpp>
Gli algoritmi di STL sono già dei cicli ottimizzati per gli specifici contenitori, ed evitano il rischio di introdurre erroneamente operazioni inefficienti.
switch (a[i]) {
 
case 1: f(); break;
Le espressioni lambda sono implementate come oggetti-funzione espansi ''inline'', e quindi sono altrettanto efficienti quanto il codice scritto sul posto.
case 2: g(); break;
case 5: h(); break;
}
</source>
 
I compilatori possono sfruttare la regolarità didelle taleistruzioni istruzione<code>switch</code> per applicare alcune ottimizzazioni, in particolare se viene applicata la linea-guida "Valori dei casi di istruzioni <code>switch</code>" di questa sezione.
Senza avere a disposizione la funzionalità lambda, nella in molti casi risulta così scomodo usare gli algoritmi STL che non ne vale la pena, a meno che l'algoritmo sia di una certa complessità, come, per esempio, nel caso di <code>std::sort</code>.
 
=== 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 piccole lacune.'''
 
I compilatori ottimizzanti, quando compilano un'istruzione <code>switch</code> i cui valori dei casi costituiscono 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 a cui inizia il codice di ogni caso, e quandoper eseguonoeseguire lol'istruzione <code>switch</code>, usano tale tabella per saltare al codice associato al numero del caso.
 
Per esempio, il seguente codice C++:
Line 207 ⟶ 226:
</source>
 
Per così pochi casi, probabilmente non ci sono molte differenze tra le due situazioni, ma con l'aumentare del numero di casi, il primo codice èdiventa più efficiente, in quanto esegue un solo ''goto calcolato'' invece di una serie di <code>if</code>.
 
=== Ordine dei casi dell'istruzione <code>switch</code> ===
Line 213 ⟶ 232:
'''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; pertanto, per cui nei casi più tipici verrebbero fatti meno confronti.
 
=== Raggruppamento di più array in un solo array di strutture ===
 
'''Invece di elaborare in parallelo due o più array della stessa lunghezza, elabora un solo array di oggetti compositicomposti.'''
 
Esempio: Invece del seguente codice:
Line 239 ⟶ 258:
</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 anche l'uso della cache del codice.
 
=== Raggruppamento di argomenti di funzione ===
 
'''Se una funzione riceve almeno sei argomenti, e viene chiamata in un ciclo con gli stessi valori tranne al più due, prima del ciclo crea una struttura che contiene tutti gli argomenti, e passa alla funzione tale struttura per puntatore a costante o riferimento a costante.'''
 
Per esempio, invece del seguente codice:
Se una funzione riceve pochi argomenti, questi vengono posti direttamente nei registri, e quindi il loro passaggio è molto veloce; ma se gli argomenti non sono pochi, 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>
Line 257 ⟶ 272:
</source>
 
scrivi il seguente codice è probabilmente più efficiente:
 
<source lang=cpp>
Line 272 ⟶ 287:
</source>
 
Se una funzione riceve pochi argomenti, questi vengono posti direttamente nei registri, e quindi il loro passaggio è molto veloce; ma se gli argomenti non sono pochi, devono essere posti nello stack ad ogni chiamata, anche se non sono glicambiati stessi delladall'iterazione precedente chiamata alla stessa funzione.
 
Se invece si passa alla funzione solo l'indirizzo di una struttura, questo indirizzo viene sicuramenteprobabilmente posto in un registro, e, dopo l'inizializzazione della struttura, solamente i campi della struttura che non sono modificati tra chiamate successive della funzione devono essere assegnati solo la prima volta.
 
=== Uso di funzioni membro di contenitori ===
Line 277 ⟶ 295:
'''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>std::find</code>, o la funzione membro <code>std::set::find</code>; ma il primo ha complessità lineare (O(n)), mentre la seconda ha complessità logaritmica (O(log(n))).
 
=== Ricerca in sequenze ordinate ===
Line 285 ⟶ 303:
'''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 tuttiTutti i citati algoritmi, dato che usano la ricerca binaria, diavente complessità logaritmica (O(log(n))), sono più veloci dell'algoritmo <code>std::find</code>, che usa la ricerca sequenziale, diavente complessità lineare (O(n)).
 
[[Categoria:Ottimizzare C++|Costrutti che migliorano le prestazioni]]