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

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Riga 299:
Gli operatori bit-a-bit (''&'', ''|'', ''^'', ''<<'', e ''>>'') sono tradotti in singole istruzioni veloci, e operano su tutti i bit di un registro in una sola istruzione.
 
== 5.4. Come ridurre le costruzioni e distruzioni di oggetti ==
 
Spesso capita che per elaborare un’espressione venga creato un oggetto temporaneo, che viene distrutto alla fine della stessa espressione in cui viene creato. Se tale oggetto è di un tipo fondamentale, il compilatore quasi sempre riesce a evitarne la creazione, e comunque la creazione e distruzione di un oggetto di un tipo fondamentale sono veloci. Se l’oggetto è invece di un tipo composito. La creazione e distruzione possono avere un costo arbitrariamente alto, in quanto comportano la chiamata di un costruttore e di un distruttore. In questa sezione si descrivono alcune tecniche per evitare che siano creati oggetti temporanei di tipo composito, e quindi che siano chiamati i relativi costruttori e distruttori.
 
=== Valore di ritorno di funzioni ===
=== 5.4.1. Per le funzioni che non siano espanse inline, cerca di dichiarare il tipo di ritorno “void” o “bool” o intero o puntatore o riferimento. Comunque, evita di dichiarare un tipo di ritorno la cui copia sposta oltre 8 byte. Se non fosse fattibile, almeno costruisci l’oggetto da ritornare nelle stesse istruzioni “return”. ===
 
'''Per le funzioni che non siano espanse ''inline'', cerca di dichiarare il tipo di ritorno ''void'' o ''bool'' o intero o puntatore o riferimento. Comunque, evita di dichiarare un tipo di ritorno la cui copia sposta oltre 8 byte. Se non fosse fattibile, almeno costruisci l’oggetto da ritornare nelle stesse istruzioni ''return''.'''
 
Nella compilazione di una funzione non espansa inline, il compilatore non può sapere se il valore di ritorno verrà usato, e quindi lo deve comunque generare. Generare un intero o un puntatore o un riferimento costa poco o niente, ma generare numeri a virgola mobile od oggetti più complessi richiede tempo. Se la copia comporta l’allocazione di risorse, il tempo richiesto è enormemente maggiore, ma anche senza allocazioni, il tempo richiesto è cresce al crescere dei numero delle word che vengono copiate quando si copia un oggetto di tale tipo. Comunque, se si costruisce l’oggetto da ritornare nelle stesse istruzioni “return”, senza quindi assegnare tale valore a una variabile, si sfrutta l’ottimizzazione garantita dallo standard detta Return Value Optimization, che previene la creazione di oggetti temporanei. Alcuni compilatori riescono a evitare la creazione di oggetti temporanei anche se sono legati a variabili locali (Named Return Value Optimization), ma in generale questo non è garantito. Per verificare se viene attuata una di tali ottimizzazioni, inserisci un contatore nei costruttori, nei distruttori, e negli operatori di assegnamento della classe dell’oggetto ritornato. Nel caso non risultassero applicate ottimizzazioni, ricorri a una delle seguenti tecniche alternative:
Line 312 ⟶ 314:
* Usa un “expression template”.
 
=== Spostamento di variabili all'esterno di cicli ===
=== 5.4.2. Se una variabile è dichiarata all’interno di un ciclo, e l’assegnamento ad essa costa di meno di una costruzione e una distruzione, sposta tale dichiarazione prima del ciclo. ===
 
'''Se una variabile è dichiarata all’interno di un ciclo, e l'assegnamento ad essa costa di meno di una costruzione e una distruzione, sposta tale dichiarazione prima del ciclo.'''
 
Se la variabile è dichiarata all’interno del ciclo, l’oggetto associato ad essa viene costruito e distrutto ad ogni iterazione, mentre se è esterna tale oggetto viene costruito e distrutto una volta sola.
 
Tuttavia, in molti casi un assegnamento costa esattamente quanto una coppia costruzione+distruzione, per cui in tali casi non ci sono vantaggi a spostare la dichiarazione all’esterno.
 
=== Operatore di assegnamento ===
Se la variabile è dichiarata all’interno del ciclo, l’oggetto associato ad essa viene costruito e distrutto ad ogni iterazione, mentre se è esterna tale oggetto viene costruito e distrutto una volta sola. Tuttavia, in molti casi un assegnamento costa esattamente quanto una coppia costruzione+distruzione, per cui in tali casi non ci sono vantaggi a spostare la dichiarazione all’esterno.
 
=== 5.4.3. '''In un overload dell’operatoredell'operatore di assegnamento (operator=), se sei sicuro che non solleverà eccezioni, non usare la tecnica “copy&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.
Line 327 ⟶ 335:
}
</source>
 
=== 5.4.4. Definisci funzioni in overload per i tipi di argomento più comune, per evitare conversioni. ===
=== Overload per evitare conversioni ===
 
'''Definisci funzioni in overload per i tipi di argomento più comune, per evitare conversioni.'''
 
Spesso si vuole chiamare una funzione passando come argomento un'espressione che non è del tipo dell'argomento, ma può essere convertita in tale tipo.
Line 337 ⟶ 348:
== 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++ if-else, for, while, do-while, switch-case, e dell'operatore di espressione condizionale ("?:").
Le istruzioni in linguaggio macchina di salto condizionato (chiamate anche “branch”), possono essere generate dalla compilazione delle istruzioni C++ if-else, for, while, do-while, switch-case, e dell’operatore di espressione condizionale (“?:”). I moderni processori elaborano efficientemente i salti condizionati solo se riescono a prevederli. In caso di errore di previsione, il lavoro che hanno già incominciato caricando nella pipeline le istruzioni successive risulta inutile e devono ripartire dall’istruzione di destinazione del salto. La predizione del salto si basa sulle iterazioni precedenti. Se queste sono regolari, il salto viene predetto correttamente. I casi migliori sono quelli in cui un’istruzione di salto viene eseguita quasi sempre o quasi mai; in tali casi, la predizione è quasi sempre corretta. Il caso peggiore è quello in cui l’istruzione di salto viene eseguita in modo casuale circa la metà delle volte; in tale caso la predizione è corretta mediamente solo la metà delle volte. Nelle porzioni critiche del codice, si dovrebbero eliminare le istruzioni di salto. Se un salto è predetto molto bene, si può ottenere un incremento di velocità solo sostituendolo con nessuna istruzione o con un’istruzione molto veloce. Se invece un salto è predetto molto male, si può ottenere un incremento di velocità anche sostituendolo con una serie di istruzioni piuttosto lenta. Ecco alcune tecniche per sostituire le istruzioni di salto con istruzioni equivalenti.
 
I moderni processori elaborano efficientemente i salti condizionati solo se riescono a prevederli.
=== 5.5.1. Usa la lookup-table binaria. ===
In caso di errore di previsione, il lavoro che hanno già incominciato a fare, caricando nella pipeline le istruzioni successive, risulta inutile e devono ripartire dall'istruzione di destinazione del salto.
 
La predizione del salto si basa sulle iterazioni precedenti.
Invece del seguente codice, in cui c e d sono espressioni costanti e b è un’espressione booleana:
Se queste sono regolari, il salto viene predetto correttamente.
<source lang=cpp>a = b ? c : d;</source>
I casi migliori sono quelli in cui un'istruzione di salto viene eseguita quasi sempre o quasi mai; in tali casi, la predizione è quasi sempre corretta.
scrivi il seguente codice:
Il caso peggiore è quello in cui l'istruzione di salto viene eseguita in modo casuale circa la metà delle volte; in tale caso, la predizione è corretta mediamente solo la metà delle volte.
<source lang=cpp>static const tipo lookup_table[] = { d, c };
a = lookup_table[b];</source>
 
Nelle porzioni critiche del codice, si dovrebbero eliminare le istruzioni di salto difficilmente predicibili.
L'espressione condizionale viene compilato in un salto condizionato. Se tale salto non è predetto bene, costa di più della lookup-table.
Se un salto è predetto molto male, anche sostituendolo con una serie di istruzioni piuttosto lenta si può ottenere un incremento di velocità.
Ecco alcune tecniche per sostituire le istruzioni di salto con istruzioni equivalenti.
 
=== La ''look-up table'' binaria. ===
 
'''Invece di un'espressione condizionale con entrambi i casi costanti, usa una ''look-up table'' a due valori.'''
 
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;
</source>
 
Sostituiscilo con il seguente codice:
 
<source lang=cpp>
static const tipo lookup_table[] = { d, c };
a = lookup_table[b];
</source>
 
L'espressione condizionale viene compilata in un salto condizionato.
Se tale salto non è predetto bene, costa di più della lookup-table.
 
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>
 
puoi scrivere il seguente:
 
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. ===
 
=== Anticipazione del calcolo degli indirizzi ===
Per esempio, in un loop la seguente istruzione:
 
'''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.
 
Nel primo caso il valore del puntatore è calcolato appena prima di accedere all'oggetto puntato, mentre nel secondo caso è calcolato nell'iterazione precedente.
== 5.6 Come ottimizzare l’uso delle cache e della memoria virtuale ==
In un processore con pipeline, nel secondo caso si può eseguire contemporaneamente l'accesso all'oggetto puntato da p e l'incremento di p.
 
== Come ottimizzare l’uso delle cache e della memoria virtuale ==
=== 5.6.1. Dichiara vicine nella stessa unità di compilazione tutte le routine appartenenti a un collo di bottiglia. ===
 
=== Avvicinare il codice ===
In tal modo, il codice macchina prodotto compilando tali routine avrà indirizzi vicini, e quindi maggiore località di riferimento del codice e dei dati statici.
 
'''Dichiara vicine nella stessa unità di compilazione tutte le routine appartenenti a un collo di bottiglia.'''
=== 5.6.2. In array medi o grandi, usa le union. ===
 
In tal modo, il codice macchina prodotto compilando tali routine avrà indirizzi vicini, e quindi maggiore località di riferimento del codice.
Le union 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.
 
Analogo effetto si ha per i dati statici dichiarati in tale codice.
=== 5.6.3. Se un oggetto medio o grande contiene dei numeri interi con un range limitato, trasformali in bit-field. ===
 
=== Le ''union'' ===
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 tre numeri che possono essere compresi tra 0 e 1000, puoi usare tre campi unsigned di 10 bit ciascuno (10 + 10 + 10 = 30, 30 <= 32), mentre per memorizzare cinque numeri che possono essere compresi tra -20 e +20, puoi usare cinque campi signed di 6 bit ciascuno.
 
'''In array medi o grandi, usa le ''union''.'''
=== 5.6.4. Nei template di classe, evita funzioni membro non banali che non dipendano da nessun parametro del template. ===
 
Le union permettono di risparmiare memoria in strutture di tipi variabili e quindi di renderle più compatte.
Piuttosto, definisci tali funzioni come non appartenenti al template e richiamale dalle funzioni membro del template. Altrimenti, il codice di tali funzioni verrà espanso ad ogni istanziazione del template, ingrandendo eccessivamente il programma.
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.
 
=== I ''bit-field'' ===
== 5.7. Come sostituire operazioni elementari con altre più veloci ==
 
'''Se un oggetto medio o grande contiene dei numeri interi con un range limitato, trasformali in ''bit-field''.'''
 
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 ''unsigned'' di un bit e tre campi ''unsigned'' 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 ===
 
'''Nei template di classe, evita funzioni membro non banali che non dipendano da nessun parametro del template.'''
 
Tutto il codice di una funzione di un template viene espanso ad ogni istanziazione distinta di tale funzione.
Se una porzione di codice non dipende dai parametri del template, ad ogni istanziazione del template verrà generato sempre lo stesso codice.
Tale replicazione di codice ingrandisce inutilmente il programma.
 
Per evitare tale replicazione di codice, definisci tali funzioni come non appartenenti al template, e richiamale dalle funzioni membro del template.
 
Una funzione di grande dimensione potrebbe avere una grande porzione che non dipende da nessun parametro del template di classe o del template di funzione.
In tal caso, in primo luogo scorpora tale porzione di codice come una funzione distinta, e poi applica questa linea-guida.
 
== Come sostituire operazioni elementari con altre più veloci ==
 
Alla metà degli anni '80 esistevano parecchie decine di trucchi di questo tipo. Tuttavia, in seguito, il miglioramento delle tecniche di compilazione ottimizzante ha incorporato nel compilatore molte di tali tecniche, e le ha quindi rese inutili; l'evoluzione delle architetture dei computer ha reso obsolete altri trucchi.
Line 394 ⟶ 457:
Pertanto qui rimangono i trucchi che possono portare dei sensibili vantaggi prestazionali sugli attuali computer e usando gli attuali compilatori.
 
=== Ordinamento dei campi di strutture ===
=== 5.7.1. Disponi le variabili membro di classi e strutture in modo che le variabili più usate siano nei primi 128 byte, e in ordine dall’oggetto più lungo a quello più corto. ===
 
'''Disponi le variabili membro di classi e strutture in modo che le variabili più usate siano nei primi 128 byte, e in ordine dall’oggetto più lungo a quello più corto.'''
Su alcuni processori, l’indirizzamento è più efficiente se la distanza dall’inizio della struttura non supera i 128 byte.
 
Su alcuni processori, l'indirizzamento è più efficiente se la distanza dall'inizio della struttura non supera i 128 byte.
 
Per esempio, usando seguente struttura, si deve usare un offset di almeno 400 byte per indirizzare i campi ''d'' e ''i'' usando un puntatore all'inizio della struttura:
 
Per esempio usando seguente struttura
<source lang=cpp>
struct {
Line 406 ⟶ 472:
};
</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, 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:
 
<source lang=cpp>
struct {
Line 416 ⟶ 482:
};
</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.
 
Ordinando gli oggetti dal più lungo al più corto si minimizzano i buchi dovuti all’allineamento.
 
Per esempio, la seguente struttura tipicamente occupa 1 (bool) + 7 (padding) + 8 (double) + 2 (short) + 2 (padding) + 4 (int) = 24 byte:
 
<source lang=cpp>
struct {
Line 429 ⟶ 495:
};
</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, tipicamente occupa 8 (double) + 4 (int) + 2 (short) + 1 (bool) + 1 (padding) = 16 byte:
 
<source lang=cpp>
struct {
Line 440 ⟶ 506:
};
</source>
tipicamente occupa 8 (double) + 4 (int) + 2 (short) + 1 (bool) + 1 (padding) = 16 byte.
 
=== Conversione da numero a virgola mobile a numero intero ===
=== 5.7.2. Sfrutta le istruzioni assembly per arrotondare a interi i numeri in virgola mobile. ===
 
'''Sfrutta le istruzioni assembly per arrotondare a interi i numeri in virgola mobile.'''
 
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).
 
Line 461 ⟶ 530:
#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.
 
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).
=== 5.7.3. Manipola i bit dei numeri interi sfruttando la conoscenza del formato di rappresentazione. ===
Se questo risultato è tollerabile o addirittura desiderato, allora questo codice è consigliabile.
Ovviamente, non è portabile su altre famiglie di processori.
 
=== Manipolazione dei bit di numeri interi ===
Una raccolta di trucchi di questo tipo si trova in http://www-graphics.stanford.edu/~seander/bithacks.html. Alcuni di questi trucchi sono in realtà già utilizzati da alcuni compilatori, altri servono per risolvere problemi particolari, altri sono utili solo su alcune piattaforme.
 
=== 5.7.4. '''Manipola i bit dei numeri a virgola mobile, dopo averli reinterpretati come numeri interi, sfruttando la conoscenza del formato di rappresentazione. ==='''
 
Una raccolta di trucchi di questo tipo si trova in http://www-graphics.stanford.edu/~seander/bithacks.html.
Alcuni di questi trucchi sono in realtà già utilizzati da alcuni compilatori, altri servono per risolvere problemi particolari, altri sono utili solo su alcune piattaforme.
 
=== Manipolazione dei bit di numeri a virgola mobile ===
 
'''Manipola i bit dei numeri a virgola mobile, dopo averli reinterpretati come numeri interi, sfruttando la conoscenza del formato di rappresentazione.'''
 
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:
 
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:
si può usare il seguente codice:
 
<source lang=cpp>
if (*(int*)&f & 0x7FFFFFFF) { // se f==0 non fare niente
Line 479 ⟶ 562:
}
</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. ===
 
=== Dimensione delle celle di array ===
L’accesso diretto alla cella di un array viene fatto moltiplicando l’indice per la dimensione di ogni cella, che è una costante. Se il secondo fattore è una potenza di due, tale moltiplicazione è molto più rapida, in quanto si realizza con uno scorrimento dei bit. Analogamente, negli array multidimensionali, tutte le dimensioni, eccetto al più la prima, dovrebbero essere potenze di due.
 
'''Assicurati che la dimensione (sizeof) delle celle non grandi degli array e dei ''vector'' sia una potenza di due, e che la dimensione delle celle grandi degli array e dei ''vector'' non sia una potenza di due.'''
Questo dimensionamento si ottiene aggiungendo alle strutture dei campi inutilizzati. Per esempio, se ogni cella è una terna di valori float, si aggiunga a ogni cella un quarto valore float “dummy”.
 
L'accesso diretto alla cella di un array viene fatto moltiplicando l'indice per la dimensione di ogni cella, che è una costante.
Tuttavia, nell’accedere alle celle di un array multidimensionale in cui l’ultima dimensione è una potenza di 2 abbastanza grande, si può cadere nel fenomeno della contesa per la cache dei dati (data cache contention), che può rallentare l’elaborazione fino a più di 10 volte. Il fenomeno si verifica solo quando le righe dell’array superano una certa dimensione, che dipende dal sistema, ma che orientativamente è dai 1024 ai 8192 byte. Pertanto, nel caso in cui un algoritmo deve elaborare un array la cui dimensione è o potrebbe essere una potenza di 2 maggiore o uguale a 256, in primo luogo si deve scoprire se si ha la contesa per la cache, e in caso affermativo evitare tale fenomeno.
Se il secondo fattore è una potenza di due, tale moltiplicazione è molto più rapida, in quanto si realizza con uno scorrimento dei bit.
Analogamente, negli array multidimensionali, tutte le dimensioni, eccetto al più la prima, dovrebbero essere potenze di due.
 
Questo dimensionamento si ottiene aggiungendo alle strutture dei campi inutilizzati. Per esempio, se ogni cella è una terna di valori float, si aggiunga a ogni cella un quarto valore float ''dummy''.
 
Tuttavia, nell'accedere alle celle di un array multidimensionale in cui l’ultima dimensione è una potenza di 2 abbastanza grande, si può cadere nel fenomeno della contesa per la cache dei dati (data cache contention), che può rallentare l’elaborazione fino a più di 10 volte. Il fenomeno si verifica solo quando le righe dell’array superano una certa dimensione, che dipende dal sistema, ma che orientativamente è dai 1024 ai 8192 byte. Pertanto, nel caso in cui un algoritmo deve elaborare un array la cui dimensione è o potrebbe essere una potenza di 2 maggiore o uguale a 256, in primo luogo si deve scoprire se si ha la contesa per la cache, e in caso affermativo evitare tale fenomeno.
 
Per scoprire l’esistenza della contesa per la cache, basta aggiungere una cella a ogni riga dell’array, ma continuare a elaborare le stesse celle di prima, e misurare se il tempo di elaborazione si riduce sostanzialmente (di almeno il 20%). In caso affermativo, si deve fare in modo che tale riduzione ci sia sempre. A tale scopo, si può adottare una delle seguenti tecniche:
* Aggiungere una o alcune celle inutilizzate alla fine di ogni riga. Per esempio l’array “double''double a[100][1024]'' potrebbe essere trasformato in “double''double a[100][1026]'', anche se nel codice si terrà conto che la dimensione utile rimane 100x1024.
* Lasciare le dimensioni appropriate dell’array, ma suddividere le matrici in blocchi rettangolari, ed elaborare tutte le celle di ogni blocco prima di passare al blocco successivo.
 
=== Espansione ''inline'' ===
=== 5.7.6. Se non usi le opzioni di ottimizzazione dell’intero programma e di espansione inline di qualunque funzione che il compilatore ritenga opportuno, prova a spostare nelle intestazioni e dichiarare inline le funzioni chiamate dai colli di bottiglia. ===
 
'''Se non usi le opzioni di ottimizzazione dell’intero programma e di espansione inline di qualunque funzione che il compilatore ritenga opportuno, prova a spostare nelle intestazioni e dichiarare inline le funzioni chiamate dai colli di bottiglia.'''
 
Come spiegato nella linea-guida 3.1.10, le singole funzioni espanse inline sono più veloci, ma un eccesso di funzioni espanse inline rallenta complessivamente il programma.
Line 497 ⟶ 587:
Prova a mettere inline un paio di funzioni per volta, fin tanto che si ottengono miglioramenti significativi della velocità.
 
=== 5.7.7.Operazioni con potenze di due ===

'''Se devi scegliere una costante intera per cui devi moltiplicare o dividere spesso, scegli una potenza di due. ==='''
 
Le operazioni di moltiplicazione, divisione e modulo tra numeri interi sono molto più veloci se il secondo operando è una potenza di due costante, in quanto in tal caso vengono implementate come scorrimenti di bit o mascherature di bit.
 
=== 5.7.8.Divisione intera per costanti ===

'''Se un numero intero ''signed'' è sicuramente non-negativo, quando lo dividi per una costante, convertilo in ''unsigned''. ===
 
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 èessere 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]]