Optimisation de code - Définition

Source: Wikipédia sous licence CC-BY-SA 3.0.
La liste des auteurs de cet article est disponible ici.

Optimisation automatique

Les compilateurs sont souvent capable de faire des optimisations locales, auxquelles aucun développeur ne penserait en première approche.

Pour le langage C, cela peut considérer :

  • les variables locales et les registres
  • les fonctions non implémentées en assembleur en tant que fonction
  • les switch, qui sont optimum.

Toutefois on peut grandement aider le compilateur en déclarant les variables avec les mots-clefs const et/ou restrict quand c'est possible. Autrement le compilateur ne peut savoir si une zone mémoire est accessible par d'autres références, et désactivera des optimisations (phénomène dit d'aliasing mémoire).

Exemples

Utilisation de variables locales pour éviter l'aliasing de mémoire

Le code C++ suivant sera en général peu optimisé par le compilateur car il est souvent incapable de savoir si le code de la boucle modifie ou non le compteur d'itérations : un pointeur ou une référence pourrait le modifier.

       void MyClass::DoSomething() const       {           for( int i=0; i<'''m_nbrElements'''; ++i )           {               void *ptr = GetSomePtr();               ....           }       }      

Dans cette version, on indique clairement qu'on utilise un nombre d'itérations fixé à l'avance et qui ne sera jamais modifié, autorisant le compilateur à effectuer des optimisations plus agressives:

       void MyClass::DoSomething()       {           '''const int nbrElements = m_nbrElements;'''           for( int i=0; i<'''nbrElements'''; ++i )           {               ....           }       }      

Une spécificité du binaire : le décalage

Une des toutes premières optimisations a été celle de la division et de la multiplication par une puissance de 2.

En effet, l'informatique actuelle repose sur le binaire, puisqu'elle utilise comme élément de base le transistor (et historiquement, auparavant le relais) qui n'autorise que deux valeurs différentes.

On a donc logiquement implémenté en langage machine les opérations de décalage à gauche et décalage à droite.

En effet, en binaire, le décalage d'un nombre d'un cran vers la gauche le multiplie par 2.

Ainsi, 2 (102) décalé de 1 bit donne 4 (1002).
5 (1012) décalé de 2 bits donne 20 (101002) : 5 * 22 = 20.

Ceci marche aussi pour la division, en décalant les bits vers la droite.

100 (11001002) décalé de 3 bits vers la droite donne 100 / 23 = 12.5 donc 12 (11002) car nous travaillons sur des nombres entiers.

La division (en dehors de ce cas) est une instruction coûteuse en temps machine, et n'est d'ailleurs toujours pas disponible sur la grande majorité des processeurs de type RISC.

Le mot clef inline du C

Le code C suivant:

       inline int f(int a, int b) {           return a * b;       }              int g (int a) {           switch (a) {               case 10:                   return f(a, a);               case 11:                case 12:                   return f(a - 2, a);               case 1200:                   return f(a - 2, a);               default:                   return f(a, a);           }       }      

Une compilation avec gcc -O4 -S donne:

               .file    "opt.c"               .text               .p2align 4,,15               .globl   g               .type    g, @function       g:               pushl   %ebp               movl    %esp, %ebp               movl    8(%ebp), %edx               cmpl    $12, %edx               jg      .L14               leal    -2(%edx), %eax               cmpl    $11, %edx               jge     .L15               movl    $100, %eax               cmpl    $10, %edx       .L17:               je      .L2               movl    %edx, %eax       .L15:               imull   %edx, %eax       .L2:               popl    %ebp               ret               .p2align 4,,7       .L14:               movl    $1437600, %eax               cmpl    $1200, %edx               jmp     .L17               .size   g, .-g               .section        .note.GNU-stack,"",@progbits               .ident  "GCC: (GNU) 3.3.2 (Mandrake Linux 10.0 3.3.2-6mdk)"      

Ce qui pourrait se traduire, pour une compréhension plus aisée, par le code C suivant:

       int g(int a) {               int eax, b;               if (a > 12)          /* cas a == 1200 */          goto L14;               eax = a - 2;        if (a >= 11)         /* cas a == 11 ou a == 12 */          goto L15;               eax=100;             /* = 10 * 10 */        b=10;              L17:        if (a == b)          /* cas a == 10 */          goto L2;                             /* cas "default" */        eax=a;       L15:        eax=eax*a;              L2:        return eax;              L14:        eax = 1437600;       /* = 1200*(1200-2) */        b = 1200;        goto L17;       }      

On peut remarquer par exemple que la fonction 'f' n'a pas été générée, mais que son code a directement été incorporé dans la fonction 'g' (le mot clef 'inline' permet de forcer ce type d'optimisation en C )

Page générée en 0.110 seconde(s) - site hébergé chez Contabo
Ce site fait l'objet d'une déclaration à la CNIL sous le numéro de dossier 1037632
A propos - Informations légales
Version anglaise | Version allemande | Version espagnole | Version portugaise