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

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Nessun oggetto della modifica
Riga 1:
In questa sezione si suggeriscono dei trucchi da adottare solamente nei colli di bottiglia, in quanto, pur rendendo il codice più veloce, ne rendono più complessa la stesura e lo rendono meno manutenibile. Inoltre, tali linee-guida in alcuni casi potrebbero sortire l’effetto indesiderato di peggiorare le prestazioni invece che migliorarle, per cui bisognerebbe sempre misurarne l’effetto prima di rilasciarle. Le tecniche di ottimizzazione sono raggruppate in base all’obiettivo che si propongono di raggiungere.
 
== 5.1. Come ridurre le operazioni di allocazione e deallocazione ==
 
=== 5.1.1. In funzioni non-ricorsive, per allocare spazio di dimensione variabile ma non grande, usa la funzione “alloca”. ===
 
Line 10 ⟶ 12:
 
È piuttosto pericolosa, in quanto, se chiamata troppe volte o con un valore troppo grande, esaurisce lo stack, e, se chiamata per allocare lo spazio in cui verranno creati oggetti dotati di distruttore, provoca resourse leak.
 
=== 5.1.2. Sposta prima dei colli di bottiglia le allocazioni di memoria e dopo i colli di bottiglia le deallocazioni. ===
 
La gestione di memoria dinamica di dimensione variabile è molto più lenta della gestione della memoria sullo stack. Analogo ragionamento va fatto per le operazioni che indirettamente provocano allocazioni, come la copia di oggetti che, direttamente o indirettamente, possiedono memoria dinamica.
 
=== 5.1.3. Prima di aggiungere elementi a oggetti “vector” o “string”, chiama “reserve” con una dimensione sufficiente per la maggior parte dei casi. ===
 
Se si aggiungono ripetutamente elementi a oggetti “vector” o “string”, ogni tanto viene eseguita una costosa operazione di riallocazione del contenuto. Per evitarla, basta allocare inizialmente lo spazio che sarà necessario.
 
=== 5.1.4. Per svuotare un oggetto " vector<T> x" senza deallocarne la memoria, usa l’istruzione "x.resize(0);"; per svuotarlo deallocandone la memoria, usa l’istruzione " vector<T>().swap(x);". ===
 
Per svuotare un oggetto vector, esiste anche l’istruzione “clear()”; tuttavia lo standard non specifica se tale istruzione conserva la capacità oppure no. Se questa istruzione deve essere eseguita spesso, e si vuole essere sicuri di evitare frequenti riallocazioni, si deve chiamare la “resize”, che secondo lo standard conserva sicuramente la capacità. Se invece si vuole essere sicuri di liberare la memoria usata da tale collezione, in quanto per un po’ tale oggetto non verrà usato, oppure verrà usato con un numero molto più piccolo di elementi, si deve chiamare la “swap” su un nuovo oggetto temporaneo vuoto.
 
=== 5.1.5. APer ogni classe concreta T che, direttamente o indirettamente, possiede della memoria dinamica, aggiungidefinisci unale funzioneappropriate membrofunzioni public“swap”. con la seguente firma:===
void swap(T&) throw();
 
In particolare, aggiungi una funzione membro public con la seguente firma:
void swap(T&) throw();
e nello stesso namespace che contiene la classe T aggiungi la seguente funzione non-membro:
void swap(T& lhs, T& rhs) { lhs.swap(rhs); }
e, se la classe non è un template di classe, nello stesso file in cui è dichiarata la classe T aggiungi la seguente funzione non-membro:
namespace std { template<> swap(T& lhs, T& rhs) { lhs.swap(rhs); } } ===
 
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.
Line 42 ⟶ 50:
Per consentire di trovare la funzione std:: swap, la funzione deve iniziare con la seguente istruzione:
 
using std::swap;
 
== 5.2. Come ridurre i costrutti che richiamano routine del supporto run-time ==
 
=== 5.2.1. Evita di usare l’operatore “typeid”. Al suo posto, cerca di usare una funzione virtual o il design pattern Visitor. ===
 
Tale operatore può richiedere un tempo superiore a quello richiesto da una semplice chiamata a funzione.
 
=== 5.2.2. Evita di usare l’operatore “dynamic_cast”. Piuttosto, usa l’operatore “typeid”. ===
 
Tale operatore può richiedere un tempo notevolmente superiore a quello richiesto da una semplice chiamata a funzione, e maggiore anche a quello richiesto dall’operatore “typeid”.
 
=== 5.2.3. Usa la specifica di eccezioni vuota (cioè aggiungi “throw()” dopo la dichiarazione) alle funzioni di cui sei certo che non solleveranno eccezioni. ===
 
Alcuni compilatori utilizzano tale informazione per ottimizzare la contabilità necessaria per gestire le eventuali eccezioni.
 
=== 5.2.4. Sposta le istruzioni “try” prima dei colli di bottiglia, e le istruzioni “catch” dopo, in modo che includano l’intero collo di bottiglia. ===
 
L’esecuzione di un blocco try/catch talvolta ha costo zero, ma altre volte comporta un rallentamento. Evita di eseguire tale blocco all’interno dei colli di bottiglia.
 
=== 5.2.5. Se il processore non contiene un’unità a virgola mobile, evita le operazioni a virgola mobile. ===
 
Gli odierni processori per sistemi desktop e server contengono hardware dedicato all’aritmetica a virgola mobile, per cui tali operazioni, eccettuate somme e sottrazioni, sono pressoché altrettanto veloci quanto quelle su numeri interi. Alcuni processori dedicati per sistemi embedded, invece, non contengono hardware dedicato all’aritmetica a virgola mobile. Pertanto tali operazioni vengono solamente emulate con lentissime funzioni di libreria. In tal caso, risulta molto più veloce utilizzare l’aritmetica intera, o meglio a virgola fissa, cioè usando degli interi intendendoli moltiplicati per un fattore di scala. Ogni numero viene moltiplicato per tale fattore in fase di input e viene per lo stesso fattore in fase di output, o viceversa.
 
=== 5.2.6. Le funzioni standard di conversione da numero intero a stringa e da numero a virgola mobile a stringa sono inefficienti. Per ottimizzarle, usa librerie non-standard o riscrivi tali funzioni. ===
 
== 5.3. Come diminuire il numero di istruzioni eseguite ==
 
=== 5.3.1. Per controllare se un numero intero “i” è compreso tra “min_i” e “max_i”, estremi inclusi, usa l’espressione “unsigned(i – min_i) <= unsigned(max_i – min_i)”. ===
 
Line 73 ⟶ 89:
 
Se le espressioni sono già unsigned, le conversioni non sono necessarie.
 
=== 5.3.2. Nelle istruzioni switch, poni i casi in ordine di probabilità decrescente. ===
 
Nella linea-guida 3.1.12 si consigliava di porre prima di casi più tipici, cioè quelli che si presume siano più probabili. Come ulteriore ottimizzazione, si dovrebbe 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.
 
=== 5.3.3. Se un certo valore intero è una costante nel codice applicativo, ma è una variabile nel codice di libreria, rendilo un parametro di template. ===
 
Line 109 ⟶ 127:
 
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.
 
=== 5.3.4. Se devi scrivere una classe base astratta di libreria tale che in ogni algoritmo applicativo si userà una sola classe derivata da tale classe base, usa il “Curiously Recurring Template Pattern”. ===
 
Line 162 ⟶ 181:
 
Altre limitazioni sono che il tipo Base è necessariamente un tipo astratto, che un oggetto di tipo Derivata1 non può essere convertito in un oggetto di tipo Derivata2 o viceversa, e che per ogni derivazione di Base tutto il codice di Base viene duplicato.
 
=== 5.3.5. Se un oggetto che implementa il pattern strategy (detto anche policy) è una costante nel codice applicativo, ma il codice di libreria deve poter gestire più strategie, rendi la classe di tale oggetto un parametro di template. ===
 
Supponi che stai scrivendo il seguente codice di libreria, che implementa il pattern strategy,
Line 219 ⟶ 239:
 
Tuttavia, tale soluzione non consente né di decidere la strategia in fase di esecuzione, né di cambiarla durante la vita dell'oggetto, e duplica il codice della strategia per ogni istanziazione di tale classe.
 
=== 5.3.6. Dovendo eseguire operazioni booleane su un insieme di singoli bit, affianca tali bit in una variabile di tipo “unsigned int”, e usa gli operatori bit-a-bit su tale oggetto. ===
 
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.
 
=== 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”. ===
 
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:
* Rendi la funzione “void”, e aggiungile un argomento passato per riferimento, che funge da valore di ritorno.
* Trasforma la funzione in un costruttore del tipo ritornato, che riceve gli stessi parametri della funzione.
* Fai in modo che la funzione restituisca un oggetto di un tipo ausiliario che ruba le risorse e le cede all’oggetto destinazione, senza copiarle.
* Usa un “rvalue reference” introdotto dallo standard C++0x.
* Usa un “expression template”.
 
=== 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. ===
* Rendi la funzione “void”, e aggiungile un argomento passato per riferimento, che funge da valore di ritorno.
* Trasforma la funzione in un costruttore del tipo ritornato, che riceve gli stessi parametri della funzione.
* Fai in modo che la funzione restituisca un oggetto di un tipo ausiliario che ruba le risorse e le cede all’oggetto destinazione, senza copiarle.
* Usa un “rvalue reference” introdotto dallo standard C++0x.
* Usa un “expression template”.
 
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 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’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 256 ⟶ 279:
 
Talvolta è possibile creare un overload della funzione originale che prende un argomento proprio del tipo corrispondente, evitando quindi la conversione.
 
== 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 (“?:”). 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.
 
5.5.1. La lookup-table binaria. Invece del seguente codice, in cui c e d sono espressioni costanti e b è un’espressione booleana:
=== 5.5.1. Usa la lookup-table binaria. ===
a = b ? c : d;
 
5.5.1. La lookup-table binaria. Invece del seguente codice, in cui c e d sono espressioni costanti e b è un’espressione booleana:
a = b ? c : d;
scrivi il seguente codice:
static const tipo lookup_table[] = { d, c };
a = lookup_table[b];
 
L'espressione condizionale viene compilato in un salto condizionato. Se tale salto non è predetto bene, costa di più della lookup-table.
Line 269 ⟶ 296:
Ovviamente questa tecnica può essere estesa a cascate di espressioni condizionali. Per esempio, invece del seguente codice:
 
a = b1 ? c : b2 ? d : b3 ? e : f;
 
scrivi il seguente
 
static const tipo lookup_table[] = { e, f, d, d, c, c, c, c };
a = lookup_table[b1 * 4 + b2 * 2 + b3];
 
=== 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:
Line 287 ⟶ 314:
 
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.
 
== 5.6 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.
 
=== 5.6.1. Dichiara vicine nella stessa unità di compilazione tutte le routine appartenenti a un collo di bottiglia. ===
 
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.
 
=== 5.6.2. In array medi o grandi, usa le union. ===
 
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.
 
=== 5.6.3. 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 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.
 
=== 5.6.4. Nei template di classe, evita funzioni membro non banali che non dipendano da nessun parametro del template. ===
 
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.
 
== 5.7. 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.
 
Pertanto qui rimangono i trucchi che possono portare dei sensibili vantaggi prestazionali sugli attuali computer e usando gli attuali compilatori.
 
=== 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. ===
 
Su alcuni processori, l’indirizzamento è più efficiente se la distanza dall’inizio della struttura non supera i 128 byte.
Line 352 ⟶ 386:
 
tipicamente occupa 8 (double) + 4 (int) + 2 (short) + 1 (bool) + 1 (padding) = 16 byte.
 
=== 5.7.2. 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:
Line 372 ⟶ 407:
 
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.
 
=== 5.7.3. Manipola i bit dei 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.
 
=== 5.7.4. 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:
 
f *= pow(2, n);
 
si può usare la seguente:
 
if (*(int*)&f & 0x7FFFFFFF) { // se f==0 non fare niente
*(int*)&f += n << 23; // aggiungi n all’esponente
}
}
 
=== 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. ===
 
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.
Line 396 ⟶ 433:
 
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 a[100][1024]” potrebbe essere trasformato in “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.
 
=== 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. ===
* Aggiungere una o alcune celle inutilizzate alla fine di ogni riga. Per esempio l’array “double a[100][1024]” potrebbe essere trasformato in “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.
 
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.
 
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.
 
Prova a mettere inline un paio di funzioni per volta, fin tanto che si ottengono miglioramenti significativi della velocità.
 
=== 5.7.7. 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. 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 è sicuramente positivo o nullo, ne velocizzi la divisione se usi le seguenti espressioni equivalenti: unsigned(s) / c e unsigned(s) % c.