"premature optimization is the root of all evil"
Dr. Donald Knuth
Optimiser un programme n'a lieu qu'en dernier recours, une fois que le programme s'exécute et donne le résultat escompte.
Ensuite SEULEMENT, et SEULEMENT SI la vitesse est un problème, optimiser, mais SEULEMENT le code qui en a réellement besoin.
Corollaire 1: Optimiser le code qui ne marche pas est inutile.
Corollaire 2: Optimiser le code qui ne pose pas de problème de vitesse est inutile.
Axiome de base des cette règle : Un code optimise est beaucoup, beaucoup plus dur a maintenir. Et en general la premiere source de bogues.
En premier lieu, il faut apporter le plus grand soin aux algorithmes choisi, utiliser des implentations de référence ou se plonger dans les livres de D. Knuth. Au besoin, il faut pouvoir prouver mathématiauement son choix. Vérifier son analyse du probleme et l'adéquation de sa solution.
Ensuite il faut tester les optimisation du compilateur.
Le codage optimisé pour inconvénient principal qu'il défigure le code source, rendant beaucoup plus difficile sa lecture et sa maintenance.
La "règle" est que l'on peut peux gagner un facteur 1000 avec un bon algorithme et au maximum un facteur 10 avec du codage optimisé.
D'abord et avant tout, il faut trouver les bouts de code et les goulot d'étranglement du code, pour savoir ce qu'il faut optimiser
Plusieurs solutions:
Avec gcc : -finline-functions
Si nécessaire, des fonctions C peuvent être re-codées comme des macros pour obtenir le même effet que l'"inline" sur les compilateurs,
Attention toutefois :
Pour toujours plus d'optimisation, il reste le code dépendant très fortement du processeur (mmx, 3dnow, etc), ou en assembleur ou avec des hacks.
switch ( queue )
{
case 0 : letter = 'W';
break;
case 1 : letter = 'S';
break;
case 2 : letter = 'U';
break;
}
ou
if ( queue == 0 )
letter = 'W';
else if ( queue == 1 )
letter = 'S';
else
letter = 'U';
Devient :
static char *classes="WSU";
letter = classes[queue];
void func1( int *data )
{
int i;
for(i=0; i<10; i++)
{
somefunc2( *data, i);
}
}
Même si data ne change pas, le compilateur ne peux pas le deviner en conséquence, la variable pointe par *data ne sera pas mise en cache :
void func1( int *data )
{
int i;
int localdata;
localdata = *data;
for(i=0; i<10; i++)
{
somefunc2( localdata, i);
}
}
Utiliser des "register unsigned int" comme compteurs (for et while). Plus rapide, plus sur et auto-documente le code.
Par exemple, un
#define compteur register unsigned int
dans le fichier d'include global
Peur d'être bloque pour réutiliser la variable dans d'autres calculs ?
Ne faites pas l'économie de variables servant a plusieurs actions différentes, cela complique le code et n'optimise rien du tout, car ces optimisations seront de toute façon faites par le compilateur.
Minimiser les boucles, groupez les si possibles, et surtout si vous les imbriquez, vérifier bien si vous ne pouvez pas l'éviter.
for(i=0; i<100; i++)
{
stuff();
}
for(i=0; i<100; i++)
{
morestuff();
}
devient :
for(i=0; i<100; i++)
{
stuff();
morestuff();
}
Quelques fois, si le code dans les boucles est minuscule, il sera mis en cache du processeur et donc deux boucles pourrait être plus rapides.
Le déploiement de boucles accélèrent leur exécution, mais augmentent la taille du binaire genere.
(souvent, optimiser implique utiliser plus de mémoire)
for(i=0; i<3; i++)
{
something(i);
}
est moins rapide que
something(0);
something(1);
something(2);
En utilisant ++i plutôt que i++ dans une boucle
while (i++)
{}
le code genere sera plus petit et plus rapide (surtout en C++):
En effet i++ donne :
stocke la valeur i dans une variable temporaire
Évalue l'expression contenant i en utilisant la variable temporaire
Incrémente i
Tandis que ++i donnera:
Incrémente i
Évalue l'expression contenant i
Tant que possible, passer les structures par référence aux fonctions mafunc(&structvar) pour éviter une recopie complète de la structure a chaque appel, et pour éviter toute modification utiliser "const" pour empêcher toute modification par la fonction.
void mafunc ( const mastruct *structvar)
{...}
Exécuter l'intégralité des itérations d'une boucle n'est pas toujours indispensable, dans ce cas break permet de s'en sortir facilement et de gagner du temps d'exécution sans rajouter de test supplémentaire.
while (i<10000)
{
if( estbonne (valeur[i]) )
{
trouve = TRUE;
}
i++
}
devient :
while (i<10000)
{
if ( estbonne (valeur[i]) )
break;
i++
}
Implémentation classique |
Implémentation optimisée |
array | hashtables |
sequential search | binary search / hash lookup |
quicksort | merge sort, radix sort |
Bresenham | lines DDA fixed point |
Opérations classiques |
Optimisations binaires pour cpu "high endian" |
x = y % 32; | x = y & 31; |
x = y * 8;z = y * 33; | x = y << 3; z = (y << 5) + y; |
x = y / w + z / w; | x = (y + z) / w; |
if (a == b && c == d && e == f )
{...} |
if ( ((a-b)|(c-d)|(e-f)) == 0 )
{...} |
if ((x & 1) || (x & 4) )
{...} |
if ( x & 5 ) {...} |
if ( x>=0 && x<8 && y>=0 && y<8
)
{...} |
if (((unsigned int) (x | y)) < 8 )
{...} |
if ( (x == 1) || (x == 2) || (x == 4) || (x == 8) || ...) | if ( x & (x-1) == 0 && x != 0 ) |
#define abs(x) \ (((x)>0)?(x):-(x)) | static long abs (long x)
{ long y; y = x>>31; return (x^y)-y; |
c = a;
a = b; b =a; |
a ^= b;
b ^= a; a ^= b; |
x = y / 4; | x = y >> 2; |