La compilation pour les nuls : compilateurs, flags, options; Comment s’y retrouver ?

Publié le 19 février 2024

Partie 1

“Ça compile donc ça fonctionne.” Avouez-le, on l’a tous entendu (ou prononcé) en école d’ingénieurs. Seulement, à y réfléchir d’un peu plus près, avons-nous vraiment bien compris en quoi consiste cette étape de compilation ?

Pour les chanceux qui répondront oui, ne partez pas. Au-delà du back to basics du compilateur, nous irons un peu plus loin avec un décryptage de travaux de recherche et d’études sur la comparaison de la performance de différents compilateurs, de l’ordonnancement des flags, et des différentes options de compilation. Pour les autres, on ne dira rien, promis.

Compilateur ? What is it?

Désolée de vous décevoir, mais non, gcc n’est pas THE compilateur. On ne va pas vous dresser la liste ici, mais l’objectif premier est de bien comprendre à quoi il sert, ce compilateur. En fait, c’est un peu comme un traducteur.

En tant que développeur, il est bien plus simple pour vous de lire du code tel qu’on l’écrit tous les jours (et pouvoir, quand il est bien écrit, le comprendre juste en le lisant) plutôt que lire de l’assembleur voire des suites de zéro ou de 1 (langage machine). Par conséquent, votre compilateur va transformer le code que vous avez écrit, vous développeur, dans le langage de votre choix, en un autre langage.

En fait, le compilateur fait même un peu plus que traduire : il va même être capable de détecter certaines erreurs dans ce fameux code source que vous, développeur, avez écrit. Oui, parce que ça nous arrive à tous ! À la fin, le programme compilé doit donner exactement la même chose que le code source originel.

Le compilateur va également être en mesure d’optimiser le code. Mais, WedoLow développerait-elle des compilateurs ? Non, on vous explique. Les optimisations proposées par le compilateur vont chercher à trouver la représentation donnant les meilleures performances tout en étant équivalentes (même si ce n’est pas toujours le cas ! On abordera le sujet plus tard). Celles-ci peuvent aussi chercher à faire correspondre au mieux le code et la cible matérielle qui va le faire tourner (en spécifiant, par exemple, le type d’architecture qui va être utilisée avec, entre autres, les flags suivants : -mcpu, -march, –target ou encore -mtune). Dans les optimisations simples réalisées par un compilateur, on peut citer la simplification algébrique ou encore la propagation de constantes.

On peut “forcer” le compilateur à proposer des optimisations ciblant un axe en particulier, que ce soit le temps d’exécution ou encore la mémoire. La partie consommation d’énergie, quant à elle, n’est pas ciblée directement par les options de compilation, comme l’explique très bien l’étude suivante[1] (Plus d’informations sur le prochain article à sortir 😉).

Finalement, notre compilateur va travailler sur le compromis temps de compilation, taille du binaire / programme, vitesse d’exécution. Ces choix vont évidemment avoir un impact sur la facilité de débugger le code produit (on ne peut pas gagner à tous les coups !).

 

Compilateur, mode d’emploi du consommateur

Si l’on revient sur nos fameux compilateurs, sont-ils équivalents ? Comment les choisir ? Malheureusement, la réponse n’est pas si simple. Une étude[2] a été, par exemple, réalisée sur deux compilateurs bien connus pour la cible x86-64, gcc et icc. Celle-ci a comparé les performances des deux compilateurs sur un ensemble d’applications développées en C++ stressant soit le CPU, soit le système d’entrées / sorties, soit les deux. Une application réalisant une transformée de Fourier rapide a aussi été benchmarkée. Les éléments sur lesquels ont été challengés les compilateurs sont les suivants :

  • les pointeurs de fonctions,
  • la présence de calculs invariants au sein d’une boucle (certains compilateurs ont toujours du mal à détecter ce motif et à le déplacer hors de la boucle),
  • les itérateurs,
  • la propagation de constantes,
  • le déroulage de boucles,
  • les structures / classes.

Les résultats, en termes de temps d’exécution, obtenus pour les différents benchmarks ne permettent pas de trancher en faveur d’un compilateur ou d’un autre (ce serait trop simple sinon). En effet, un compilateur va finalement être plus doué qu’un autre sur une problématique spécifique (comme gcc et la propagation de constantes par exemple).

L’un des axes sur lesquels on peut jouer quand on cherche à améliorer les performances d’un code grâce à notre compilateur, c’est le niveau d’optimisation sélectionné. C’est-à-dire, se poser la question : est-ce que je compile en -O0, -O2, -O3, ou même -OS ? Et un nouveau piège face à nous : ils ne veulent absolument pas dire la même chose pour un compilateur ou l’autre. Pour icc et gcc ce n’est pas tout du équivalent : en effet, certaines optimisations sont tolérées en -O2 pour icc alors qu’elles ne le sont pas en -O3 pour gcc  !

Au-delà des niveaux d’optimisation, un autre axe sur lequel jouer plus finement est l’ajout de flags de compilation. Ceux-ci vont permettre de customiser le schéma d’optimisation qui va être exploré par le compilateur. Par exemple, le fait d’inliner les fonctions (attention au compromis taille / temps d’exécution !), de dérouler les boucles ou encore d’autoriser certaines optimisations sur des opérations en virgule flottante (attention dans ce cas, à la perte de précision possible). Il est donc essentiel de pouvoir déterminer précisément quels sont les flags intéressants (et acceptables) pour un cas d’usage spécifique. Ensuite, juste pour pimenter un peu plus le tout, la combinaison des flags ainsi que leur ordonnancement peut avoir un impact sur les performances du programme. Des techniques de machine learning ont été proposées à l’état de l’art de manière à être en mesure d’automatiquement proposer la bonne combinaison de flags ainsi que leur ordre. Autant dire qu’en termes de frugalité, nous ne ciblons pas ces techniques pour le moment chez WedoLow. Mais il est intéressant de savoir que jouer sur ces paramètres, qui peuvent paraître anodins, peut avoir un impact significatif sur les performances.

Il va sans dire que les flags utilisés ont un impact sur la performance de l’application elle-même mais également sur le temps de compilation. Ce dernier a également fait l’objet de l’étude, mais nous ne nous y attacherons pas ici.

 

 

En résumé

Au-delà des premières bonnes pratiques présentées dans notre article sur l’éco-conception logicielle, on constate qu’il existe encore d’autres éléments sur lesquels regarder le compromis qualité / performance ou efficience.

Bien que l’on rentre un peu plus dans les profondeurs de l’informatique, il peut être intéressant de regarder les performances de différents compilateurs ainsi que des flags et options associés, lorsque l’on a la main pour choisir ces différents éléments. Pas de règle générale malheureusement, mais un peu de pratique pourra vous aider à déterminer votre combinaison gagnante !

L’un des moyens de tester rapidement ces différents compilateurs, flags et options peut être par le biais des outils et de l’expertise apportés par WedoLow.

Contactez notre équipe d’experts pour échanger sur le sujet : sales@wedolow.com

 

 

[1] Yuki, Tomofumi, and Sanjay Rajopadhye. « Folklore Confirmed: Compiling for Speed Compiling for Energy. » International Workshop on Languages and Compilers for Parallel Computing. Cham: Springer International Publishing, 2013.

[2] Botezatu, Mirela. « A study on compiler flags and performance events. » Conseil Européen pour la Recherche Nucléaire (2012).