Encore un pamphlet sur `inline`

Article original : Yet another pamphlet about inlining | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Introduction

Le sujet de cette semaine sera le mot-clé inline.

C’est un sujet de longue date, qui pour moi est clos depuis longtemps. À chaque fois que je le rouvre pour me re-documenter à son sujet, j’en arrive aux mêmes conclusions.

Cela dit, je lis très souvent du code qui utilise mal ce mot-clé, ce qui finit toujours par des situations dangereuses et contre-productives.

Je vais essayer ici de résumer tout ce qu’il faut savoir à propos de inline, les avantages, les inconvénients et lister les situations où il faut ou ne faut pas l’utiliser.

Définition

Je vais commencer par une définition académique donnée par inline specifier – cppreference.com et traduite par mes soins:

Le but originel du mot-clé inline était de servir d’indicateur pour l’optimiseur que la substitution du corps de la fonction était préférable à son appel, c’est-à-dire au lieu d’exécuter l’instruction CPU d’appel de la fonction pour transférer le contrôle au corps de la fonction, une copie de ce corps de fonction est créé par l’appel de fonction sans générer effectivement d’appel. Cela permet d’éviter les instructions supplémentaires créées par l’appel de fonction (passer les argument et retourner le résultat) mais peut avoir pour conséquences un plus gros exécutable, vu que le corps de la fonction sera copié plusieurs fois.

Étant donné que ce mot-clé n’implique pas de contrainte forte, les compilateurs sont libres de substituer le corps de la fonction là où le mot-clé inline n’est pas précisé et de générer des appels de fonction là où le mot-clé inline est précisé. Ces choix d’optimisation ne changent pas les règles à propos des multiples définitions.

Il faut retenir deux choses de cette définition :

  • Le mot-clé inline permet d’éviter des instructions assembleur inutiles, remplaçant l’appel d’une fonction par directement le code qu’elle implémente.
  • Il s’agit simplement d’une indication donnée au compilateur, qu’il est libre de respecter ou non.

Avec cela en tête, faisons un petit tour des avantages et des inconvénients.

Avantages

Tout d’abord, gardons en tête que même s’il ne s’agit que d’une indication, le compilateur peut effectivement la suivre. Il a le choix de le faire, mais c’est une possibilité plausible. Il suffit d’imaginer le compilateur le moins intelligent du monde, qui prendrait à la lettre les indications de inline. Il suit parfaitement le standard (selon le standard C++11 §12.1.5) et le mot-clé est utile dans ce cas.

Selon l’article What is C++ inline functions – C++ Articles (cplusplus.com)2, les avantages sont :

  1. Cela accélère le programme en évitant les instructions d’appel de fonction.
  2. Cela économise de l’espace sur la stack car cela ne push/pull pas les paramètres de fonction.
  3. Cela économise l’instruction de retour de la fonction.
  4. Cela créé de la localité de référence, en utilisant le cache d’instruction.
  5. En la marquant comme inline, vous pouvez mettre la définition d’une fonction dans un header (i.e. cela pourra être inclus dans plusieurs unités de compilation sans déranger le linker).

Les points 1, 2 et 3 sont globalement les principaux bénéfices de cette fonctionnalité et le but originel de ce mot-clé. Pour ceux qui connaissent un peu l’assembleur, cela évite notamment de pousser les paramètre de la fonction sur la pile, ce qui coûte de nombreuses instructions.

Le point 4 semble être un effet de bord non négligeable, mais comme le cache d’instruction est loin d’être ma spécialité, je ne m’épancherai pas sur ce sujet.

Le point 5 n’est un avantage que dans certaines situations spécifique, mais un avantage malgré tout.

Désavantages

Selon l’article What is C++ inline functions – C++ Articles (cplusplus.com)2, les désavantages sont :

  1. Cela augmente la taille de l’exécutable.
  2. L’inlining est résolu à la compilation. Cela signifie que si vous changez le code de la fonction inlinée, vous devrez re-compiler tous le code utilisant cette fonction.
  3. Ça augmente la taille de votre header avec des informations qui ne sont pas utiles pour les utilisateurs.
  4. Comme mentionné ci-dessus, cela augmente la taille de l’exécutable, ce qui peut augmenter les fautes de pagination en mémoire, et de ce fait diminuer les performances de votre programme.
  5. Cela peut être dérangeant dans les projets ayant des contraintes de mémoire, comme les projets embarqués.

Les point 1 et 4, qui sont méconnus parmi les développeurs, sont la raison principale pour laquelle le mot-clé inline peut diminuer les performances de votre programme. Il est important de s’en souvenir quand on l’utilise.

Le point 2 peut être un inconvénient majeur, en fonction de la nature de votre projet, mais n’arrive pas excessivement souvent selon mon expérience.

Le point 3 est, d’après moi, le plus gros désavantage de cette fonctionnalité. Pour avoir du code maintenable, vous vous devez d’être clair et organisé. inline est un point noir vis-à-vis de cette notion.

Le point 5 concerne des projets spécifiques, aussi je ne m’étendrai pas dessus. Mais gardez en tête que si vous avez des contraintes de mémoire, inline peut avoir des conséquences.

Conclusion : quand faut-il utiliser inline ?

Éviter les instructions d’appel de fonction en inlinant votre code est seulement utile dans les sections critiques de votre programme.

N’oubliez pas la loi de Paretos : « 80% de l’exécution ne se déroule que dans 20% du code. ». Cela signifie que votre programme passe la plupart de son temps dans des goulots d’étranglement. De fait, si vous inlinez du code qui n’est pas dans un goulot, cela aura peu voire pas d’effet sur vos performances, tout en augmentant la taille de l’exécutable et rendant votre code moins lisible.

Ce qui m’a poussé à écrire cet article est le fait que durant ma vie de codeur, un bon 95% des inline que j’ai pu voir sur des projets industriels étaient utilisé dans du code non-critique.

Il n’y a absolument aucun intérêt à réduire la lisibilité du code dans ce but.

Voici mon conseil : n’utilisez pas inline à moins que vous soyez sûr(e) à 100% qu’il sera appelé dans un goulot d’étranglement.

C’est le seul moyen d’avoir du code à la fois propre et efficace.

Une autre manière d’inliner à éviter

Je vais terminer cet article en disant un mot à propos d’une pratique à particulièrement éviter.

Il s’agit des fonctions implémentées de la manière suivante :

inline void setValue(int i) { m_value = i; }

Cette pratique empêche certains debuggers de faire correctement leur travail.

Par exemple, sous VisualStudio, su vous mettez un point d’arrêt dans cette fonction et qu’il percute, le debugger ne sera pas capable de vous donner la valeur de m_value.

Alors s’il vous plaît, ne faites pas ça. Cela ne coûte pas grand chose de rajouter un ou deux retours à la ligne.

Sur ce, je vous dis à la semaine prochaine !

Article original : Yet another pamphlet about inlining | Belay the C++ (belaycpp.com)
Traductrice : Chloé Lourseyre

Laisser un commentaire