Optimisation des compilateurs/Les optimisations de la taille du code

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

Si la vitesse des programmes peut être optimisée, les compilateurs peuvent aussi jouer sur la taille du programme. La taille d'un programme en elle-même n'est pas importante pour des programmes PC puisque la majorité d'entre eux possèdent plusieurs gibioctets de RAM, ce qui laisse une grande marge. Par contre, diminuer la taille du code a des effets indirects sur les performances de ces machines en économisant de la mémoire cache. L'optimisation de la taille du code a cependant une plus grande importance sur certaines applications embarquées ou destinées à l'informatique industrielle, qui doivent faire avec des machines peu puissantes.

Il faut signaler que, parmi les optimisations précédentes, certaines ont un effet sur la taille du code. C'est le cas des simplifications de calculs, des optimisations qui permettent de supprimer des calculs inutiles, la propagation des constantes, le constant folding, la fusion de boucle, l’élimination des branchements inutiles, etc. Mais l'inverse est aussi vrai : certaines optimisations qui rendent les programmes plus rapides augmentent la taille du code. C'est notamment le cas de l'inlining, du déroulage de boucles, etc. Aussi, ne vous étonnez pas si les optimisations que nous allons voir sont, pour certaines, l'exact inverse de certaines optimisations vues précédemment.

Le choix des instructions[modifier | modifier le wikicode]

La première optimisation consiste à choisir les instructions les plus courtes quand on peut utiliser plusieurs instructions pour une même opération. L'exemple classique sur les processeurs x86 est la mise à zéro d'un registre : il vaut mieux effectuer un XOR qu'un MOV. Par exemple, XOR EAX EAX est plus court que MOV EAX 0. Le seul problème, c'est que les instructions courtes ne sont pas forcément les plus rapides, or le compilateur a tendance à choisir des instructions plus longues, mais plus rapides.

La jonction de branchements[modifier | modifier le wikicode]

Il est possible que dans un if…else, certaines lignes de code soient recopiées à la fois dans le if et dans le else. Il arrive de plus que le résultat de ces lignes de code soit totalement indépendant du contenu du else ou du if.

Prenez cet exemple :

if (a > 0)
{
    --a;
    ++b;
}
else 
{
    ++a;
    ++b;
}

On voit bien que la ligne ++b peut être factorisée vu qu'elle est totalement indépendante du contenu du if et du else. Un compilateur peut effectuer automatiquement cette transformation, ce qui donne :

++b;

if (a > 0)
{
    --a;
}
else 
{
    ++a;
}

L'abstraction de procédure[modifier | modifier le wikicode]

Une autre technique très utile est l'abstraction de procédure, aussi appelé factorisation de code. Ce nom traduit bien ce que fait cette technique : elle factorise des morceaux de code identique en un seul. Plus précisémment, elle détecte des morceaux de code présents en plusieurs exemplaires et les transforme en fonction : les exemplaires sont alors retirés du programme, et sont remplacés par un appel à la fonction créée. On peut voir cette technique comme l'exact inverse de l'inlining.

Autant vous dire que décider quand inliner ou utiliser l'abstraction de procédure est généralement un véritable casse-tête. Généralement, les compilateurs disposent de paramètres de compilation pour spécifier s'il faut optimiser pour la taille du code ou pour la vitesse : l'abstraction de procédure est utilisée quand on choisit l'option taille du code, alors que l'inlining est utilisé quand on choisit l'option vitesse.

La suppression de code mort[modifier | modifier le wikicode]

Un compilateur peut aussi détecter les morceaux de code qui ne seront jamais exécutés. Par exemple, il est capable de remarquer que le code d'un else ou d'un if dont la condition est constante ne sera jamais exécuté et peut alors supprimer ce code. Cela permet de gagner de la place assez facilement, surtout quand la propagation de constante fait son petit effet.