Pourquoi l’inline est-il considéré plus rapide qu’un appel de fonction?

Maintenant, je sais que c’est parce qu’il n’y a pas de surcharge dans l’appel d’une fonction, mais est-ce que le fait d’appeler une fonction est vraiment trop lourd (et vaut la peine d’être inséré)?

D’après ce dont je me souviens, lorsqu’une fonction est appelée, dites f (x, y), x et y sont poussés sur la stack, et le pointeur de stack saute à un bloc vide et commence l’exécution. Je sais que c’est une simplification excessive, mais est-ce que je manque quelque chose? Quelques poussées et un saut pour appeler une fonction, y a-t-il vraiment trop de frais généraux?

Faites-moi savoir si j’oublie quelque chose, merci!

Mis à part le fait qu’il n’y a pas d’appel (et donc pas de dépenses associées, comme la préparation des parameters avant l’appel et le nettoyage après l’appel), il y a un autre avantage important de l’inline. Lorsque le corps de la fonction est en ligne, son corps peut être réinterprété dans le contexte spécifique de l’appelant. Cela pourrait immédiatement permettre au compilateur de réduire et d’optimiser davantage le code.

Pour un exemple simple, cette fonction

 void foo(bool b) { if (b) { // something } else { // something else } } 

nécessitera un twigment réel si elle est appelée comme une fonction non incorporée

 foo(true); ... foo(false); 

Cependant, si les appels ci-dessus sont intégrés, le compilateur sera immédiatement en mesure d’éliminer les twigments. Essentiellement, dans le cas ci-dessus, l’insertion permet au compilateur d’interpréter l’argument de la fonction comme une constante de compilation (si le paramètre est une constante à la compilation) – ce qui n’est généralement pas possible avec des fonctions non incorporées.

Cependant, cela ne se limite même pas à cela. En général, les possibilités d’optimisation de l’inlining sont beaucoup plus importantes. Pour un autre exemple, lorsque le corps de la fonction est inséré dans le contexte de l’appelant spécifique , le compilateur en général pourra propager dans le code de fonction incorporé les relations connues sous forme d’alias présentes dans le code appelant, ce qui permettra d’optimiser code de la fonction mieux.

Encore une fois, les exemples possibles sont nombreux, tous issus du fait fondamental que les appels intégrés sont immergés dans le contexte de l’appelant spécifique , permettant ainsi diverses optimisations inter-contexte, ce qui ne serait pas possible avec des appels non incorporés. Avec l’inlining vous obtenez essentiellement de nombreuses versions individuelles de votre fonction d’origine, chaque version est personnalisée et optimisée individuellement pour chaque contexte d’appelant spécifique. Le prix de ceci est évidemment le danger potentiel de ballonnement du code, mais s’il est utilisé correctement, il peut offrir des avantages notables en termes de performances.

“Quelques poussées et un saut pour appeler une fonction, y a-t-il vraiment trop de frais généraux?”

Cela dépend de la fonction.

Si le corps de la fonction n’est qu’une instruction de code machine, la surcharge d’appel et de retour peut atteindre plusieurs centaines de pour cent. Dites, 6 fois, 500% de frais généraux. Ensuite, si votre programme ne contient que des millions d’appels à cette fonction, sans incrustation, vous avez augmenté le temps d’exécution de 500%.

Cependant, dans l’autre sens, l’insertion peut avoir un effet néfaste, par exemple parce que le code qui ne serait pas incliné dans une page de mémoire ne le permet pas.

La réponse est donc toujours l’optimisation, en premier lieu MEASURE.

Il n’y a pas d’activité d’appel et de stack, ce qui économise certainement quelques cycles de processeur. Dans les processeurs modernes, la localité du code est également importante: faire un appel peut vider le pipeline d’instructions et forcer le processeur à attendre que la mémoire soit extraite. Cela est très important dans les boucles serrées, car la mémoire primaire est beaucoup plus lente que celle des processeurs modernes.

Cependant, ne vous inquiétez pas si votre code est appelé plusieurs fois dans votre application. T’inquiète beaucoup si on t’appelle des millions de fois pendant que l’utilisateur attend des réponses!

Le candidat classique pour l’inlining est un accesseur, comme std::vector::size() .

Avec inlining activé, il ne s’agit que de récupérer une variable depuis la mémoire, probablement une seule instruction sur toutes les architectures. Les “quelques poussées et un saut” (plus le retour) sont facilement multiples .

Ajoutez à cela le fait que plus le code est visible à la fois pour un optimiseur, mieux il peut faire son travail. Avec beaucoup d’inline, il voit beaucoup de code à la fois. Cela signifie qu’il peut être capable de conserver la valeur dans un registre de CPU et de libérer complètement le coûteux voyage en mémoire. Nous pourrions maintenant aborder une différence de plusieurs ordres de grandeur .

Et puis theres modèle meta-programmation . Parfois, cela se traduit par l’appel récursif de nombreuses petites fonctions, juste pour récupérer une valeur unique à la fin de la récursivité. (Pensez à récupérer la valeur de la première entrée d’un type spécifique dans un tuple contenant des dizaines d’objects.) Lorsque l’inlining est activé, l’optimiseur peut accéder directement à cette valeur (qui, rappelez-vous, peut être dans un registre). appelle à accéder à une seule valeur dans un registre de CPU. Cela peut transformer un terrible joueur de performance en un programme agréable et rapide.


Cacher l’état en tant que données privées dans des objects (encapsulation) a ses coûts. L’inlining faisait partie de C ++ dès le début afin de minimiser ces coûts d’abstraction . À l’époque, les compilateurs étaient nettement moins efficaces qu’ils le faisaient aujourd’hui dans la détection des bons candidats à l’inclusion (et au rejet des mauvais candidats).
De nos jours, les compilateurs sont réputés plus intelligents que nous ne le sums en ligne. Les compilateurs sont en mesure d’intégrer des fonctions automatiquement ou de ne pas aligner les fonctions des utilisateurs marqués comme étant en inline , même s’ils le peuvent. Certains disent que l’inclusion devrait être complètement laissée au compilateur et que nous ne devrions même pas déranger les fonctions de marquage en inline . Cependant, je n’ai pas encore vu d’étude approfondie indiquant si cela en valait toujours la peine ou non. Donc, pour le moment, je vais continuer à le faire moi-même et laisser le compilateur le remplacer s’il pense qu’il peut faire mieux.