Optimisation des compilateurs/Les optimisations liées aux constantes

Un livre de Wikilivres.
Sauter à la navigation Sauter à la recherche

Beaucoup des optimisations des compilateurs sont liées à des expressions qui manipulent des constantes. Par exemple, certains calculs dont une opérande est constante peuvent être simplifiés. Nous allons les détailler ci-dessous.

L'élimination des calculs constants[modifier | modifier le wikicode]

D'ordinaire, ces simplifications se font quand un calcul a une ou plusieurs opérandes constantes, dont la valeur permet des simplifications. Le cas le plus simple est clairement celui où toutes les opérandes sont constantes : le compilateur peut alors faire le calcul lui-même et simplement mettre le résultat directement dans le code du programme. Par exemple, des calculs du style : int a = 2 * 3.1415926535 sont simplifiables. En clair, dès qu'une expression a tous ses opérandes constants, un compilateur peut parfois calculer le résultat lui-même et remplacer l'expression par son résultat : on parle de constant folding.

Les simplifications de calculs avec une opérande constante[modifier | modifier le wikicode]

Le cas où seule une opérande est constante peut aussi donner lieu à certaines simplifications. Pour en donner un exemple, une multiplication par deux peut être efficacement exécutée par un décalage à gauche ou par une simple addition de la valeur à elle-même. Comme autre exemple, prenons le cas classique de la multiplication. On peut déduire le résultat de l'opérande sans avoir à faire le calcul : multiplier une expression par 0 ou par 1 est inutile, le résultat étant connu à l'avance. Même chose pour une addition ou soustraction avec 0, ou une division par 1. Le compilateur peut reconnaitre de tels cas et supprimer l'instruction inutile.

Les simplifications de fonctions avec des arguments constants[modifier | modifier le wikicode]

Quand une fonction est appelée avec tous ses arguments constants, le compilateur peut effectuer diverses simplifications. Dans ce cas , le compilateur peut simplement calculer le résultat renvoyé par la fonction. Par exemple prenons ce code :

int abs (int n)
{
  if (n   0)
    return n ;
  else
    return -n ;
}

int main(int n)
{
  int a = -4 ;
  return abs(a) ;
}

On voit que la fonction est appelée avec tous ses arguments constants. Le compilateur peut alors calculer son résultat et remplacer l'appel par une simple affectation du résultat. Le code se simplifie alors en :

int abs (int n)
{
  if (n   0)
    return n ;
  else
    return -n ;
}

int main(int n)
{
  return -4 ;
}

Mais attention, cette optimisation n'est sont valable que dans un cas bien précis : celui des expressions et fonctions qui donnent le même résultat pour des opérandes identiques. Il existe des calculs qui ne respectent pas cette règle, notamment certaines fonctions : ce sont des expressions ou fonctions dites impures. Et un compilateur n'a pas vraiment de moyens pour savoir si une fonction est pure ou impure : il devrait regarder et analyser son code, ce qui demanderait trop de ressources pour un résultat qui n'en vaut pas le coup. Dans ces conditions, le compilateur considère toutes les fonctions comme étant impures. La conséquence, c'est que de très nombreuses optimisations seront rendues impossibles si jamais on trouve une fonction au mauvais endroit dans le code source.