71 votes

Quelle est l'incidence de l'appel d'une fonction en C++ ?

De nombreux ouvrages parlent de l'utilisation des fonctions en ligne pour "éviter la surcharge d'un appel de fonction". Cependant, je n'ai pas vu de données quantifiables. Quelle est la surcharge réelle d'un appel de fonction, c'est-à-dire quelle sorte d'augmentation des performances obtenons-nous en utilisant les fonctions inline ?

0 votes

Vote de clôture pour cause de "trop large". On ne peut pas vraiment répondre à cette question de manière sensée. Cela dépend du compilateur, de la détection des points chauds, du processeur (mise en cache, exécution spéculative, mise en mémoire tampon des cibles de branchement), et de nombreux autres facteurs.

49voto

Eclipse Points 27662

Sur la plupart des architectures, le coût consiste à sauvegarder tous (ou certains, ou aucun) des registres sur la pile, à pousser les arguments de la fonction sur la pile (ou à les mettre dans des registres), à incrémenter le pointeur de pile et à sauter au début du nouveau code. Ensuite, lorsque la fonction est terminée, vous devez restaurer les registres de la pile. Cette page web contient une description des différentes conventions d'appel.

La plupart des compilateurs C++ sont désormais suffisamment intelligents pour mettre les fonctions en ligne à votre place. Le mot-clé inline n'est qu'un indice pour le compilateur. Certains font même de l'inline à travers les unités de traduction lorsqu'ils décident que c'est utile.

10 votes

Sur x86 (et beaucoup d'autres arches) tous les registres n'ont pas besoin d'être sauvegardés car ils sont censés être modifiés par des appels de fonction. La convention d'appel C sur x86 ne préserve généralement pas eax, ecx et edx.

1 votes

Pousser tous les paramètres de fonction sur la pile est l'ABI du C. Le C++ ne spécifie pas d'ABI spécifique dans le cadre de la norme (contrairement au C). Cela permet à chaque compilateur d'optimiser selon ses besoins. C'est pourquoi la plupart des compilateurs C++ ne mettent pas tous les paramètres sur la pile.

9 votes

@Martin York : L'ABI du C ne fait pas partie de la norme - c'est impossible, la norme est agnostique en matière d'architecture, alors que l'ABI dépend de l'architecture. Les ABI standardisées pour le C, qui lui permettent d'être utilisé comme un langage d'échange de base et de colle, sont faites par le fabricant de l'OS ou de la puce. BeOS a une ABI C++.

13voto

nedruod Points 750

Il y a la réponse technique et la réponse pratique. La réponse pratique est que cela n'aura jamais d'importance, et dans le cas très rare où cela en aurait, la seule façon de le savoir est de faire des tests profilés.

La réponse technique, à laquelle votre littérature fait référence, n'est généralement pas pertinente en raison des optimisations du compilateur. Mais si vous êtes toujours intéressé, elle est bien décrite par Josh .

Pour ce qui est du "pourcentage", il faudrait connaître le coût de la fonction elle-même. En dehors du coût de la fonction appelée, il n'y a pas de pourcentage car vous comparez à une opération à coût nul. Pour le code inlined, il n'y a pas de coût, le processeur passe simplement à l'instruction suivante. L'inconvénient de l'inling est une taille de code plus importante qui manifeste ses coûts d'une manière différente des coûts de construction/arrachage de la pile.

9voto

Mecki Points 35351

Votre question est l'une des questions qui n'a pas de réponse que l'on pourrait qualifier de "vérité absolue". L'overhead d'un appel de fonction normal dépend de trois facteurs :

  1. Le CPU. L'overhead des CPU x86, PPC et ARM varie beaucoup et même si vous restez sur une seule architecture, l'overhead varie également beaucoup entre un Intel Pentium 4, un Intel Core 2 Duo et un Intel Core i7. L'overhead peut même varier sensiblement entre un processeur Intel et un processeur AMD, même si les deux fonctionnent à la même vitesse d'horloge, car des facteurs tels que la taille du cache, les algorithmes de mise en cache, les modèles d'accès à la mémoire et l'implémentation matérielle de l'opcode d'appel lui-même peuvent avoir une influence considérable sur l'overhead.

  2. L'ABI (Application Binary Interface). Même avec le même processeur, il existe souvent différentes ABI qui spécifient comment les appels de fonction passent les paramètres (via les registres, via la pile ou via une combinaison des deux) et où et comment l'initialisation et le nettoyage du cadre de la pile ont lieu. Tout ceci a une influence sur l'overhead. Différents systèmes d'exploitation peuvent utiliser différentes ABI pour la même unité centrale ; par exemple, Linux, Windows et Solaris peuvent tous trois utiliser une ABI différente pour la même unité centrale.

  3. Le compilateur. Le respect strict de l'ABI n'est important que si les fonctions sont appelées entre des unités de code indépendantes, par exemple si une application appelle une fonction d'une bibliothèque système ou si une bibliothèque utilisateur appelle une fonction d'une autre bibliothèque utilisateur. Tant que les fonctions sont "privées", c'est-à-dire non visibles en dehors d'une bibliothèque ou d'un binaire donné, le compilateur peut "tricher". Il peut ne pas suivre strictement l'ABI mais utiliser des raccourcis qui permettent d'appeler les fonctions plus rapidement. Par exemple, il peut passer les paramètres dans un registre au lieu d'utiliser la pile ou il peut sauter la configuration et le nettoyage de la trame de la pile si ce n'est pas vraiment nécessaire.

Si vous voulez connaître l'overhead pour une combinaison spécifique des trois facteurs ci-dessus, par exemple pour Intel Core i5 sur Linux en utilisant GCC, la seule façon d'obtenir cette information est de comparer la différence entre deux implémentations, l'une utilisant des appels de fonction et l'autre où vous copiez le code directement dans l'appelant ; de cette façon, vous forcez l'inlining à coup sûr, puisque la déclaration inline n'est qu'une indication et ne conduit pas toujours à l'inlining.

Cependant, la vraie question ici est : Le montant exact des frais généraux a-t-il vraiment de l'importance ? Une chose est sûre : Un appel de fonction a toujours une surcharge. Il peut être petit, il peut être grand, mais il existe à coup sûr. Et aussi petit soit-il, si une fonction est appelée assez souvent dans une section critique pour les performances, l'overhead aura une certaine importance. L'alignement rend rarement votre code plus lent, à moins que vous n'en fassiez trop ; il rendra cependant le code plus gros. Les compilateurs d'aujourd'hui sont plutôt bons pour décider eux-mêmes de l'opportunité d'une mise en ligne ou non, de sorte que vous n'avez pratiquement jamais à vous creuser la tête à ce sujet.

Personnellement, j'ignore complètement l'inlining pendant le développement, jusqu'à ce que j'ai un produit plus ou moins utilisable que je peux profiler et seulement si le profilage me dit, qu'une certaine fonction est appelée très souvent et aussi dans une section critique de l'application, alors j'envisagerai le "force-inlining" de cette fonction.

Jusqu'à présent ma réponse est très générique, elle s'applique aussi bien au C qu'au C++ et à l'Objective-C. En guise de conclusion, permettez-moi de dire quelque chose sur le C++ en particulier : Les méthodes virtuelles sont des appels de fonction indirects doubles, ce qui signifie qu'elles ont un coût d'appel de fonction plus élevé que les appels de fonction normaux et qu'elles ne peuvent pas être intégrées. Les méthodes non virtuelles peuvent être inlined par le compilateur ou non, mais même si elles ne le sont pas, elles sont toujours beaucoup plus rapides que les méthodes virtuelles, donc vous ne devriez pas rendre les méthodes virtuelles, à moins que vous n'ayez vraiment l'intention de les surcharger ou de les faire surcharger.

8voto

Mark Ransom Points 132545

Le montant de la surcharge dépendra du compilateur, du processeur, etc. Le pourcentage de surcharge dépendra du code que vous insérez. La seule façon de le savoir est de prendre votre code et le profil dans les deux sens - c'est pourquoi il n'y a pas de réponse définitive.

5voto

Don Neufeld Points 12803

Pour les très petites fonctions, l'inlining a du sens, car le coût (faible) de l'appel de fonction est important par rapport au coût (très faible) du corps de la fonction. Pour la plupart des fonctions de plus de quelques lignes, ce n'est pas une grande victoire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X