Optimisations en langage C

Quand Ou Comment Code

Quand optimiser ?

"premature optimization is the root of all evil"

Dr. Donald Knuth

REGLE A RESPECTER OBLIGATOIREMENT :

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.

Quel type d'optimisation appliquer?

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é.

Chemins d'optimisations

Détecter la lenteur

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:

Opérations Lentes

Opérations Rapides

"inlining"

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.

Quelques optimisations:

règles générales

Les Bases

Utiliser des tableaux pour les choix multiples :

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];

Utiliser des variables locales pour accélérer les accès aux variables pointées

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);

         }

}

Les compteurs de boucles

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.

Boucles

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.

"Loop Unrolling" et "Dynamic Loop Unrolling"

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);

Incrémentation de boucle

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

Structures, Passage par référence

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)

{...}

Utiliser Break

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++

}

Code

Algorithmes et structures

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érateurs Binaires (Attention dépend de l'architecture machine)

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;

Suggestions ?