L'extraction de fonctionnalités dans des méthodes ou des fonctions est indispensable pour la modularité, la lisibilité et l'interopérabilité du code, en particulier dans la POO.
Mais cela signifie que davantage d'appels de fonctions seront effectués.
Comment la division de notre code en méthodes ou fonctions affecte-t-elle réellement les performances dans les langages modernes * ?
* Les plus populaires: C, Java, C ++, C #, Python, JavaScript, Ruby ...
Réponses:
Peut être. Le compilateur pourrait décider "hé, cette fonction n'est appelée que quelques fois, et je suis censé optimiser la vitesse, donc je vais juste intégrer cette fonction". Essentiellement, le compilateur remplacera l'appel de fonction par le corps de la fonction. Par exemple, le code source ressemblerait à ceci.
Le compilateur décide de s'aligner
DoSomethingElse
et le code devientLorsque les fonctions ne sont pas intégrées, oui, il y a un impact sur les performances pour effectuer un appel de fonction. Cependant, c'est un coup si minuscule que seul le code extrêmement performant va se soucier des appels de fonction. Et sur ces types de projets, le code est généralement écrit en assembleur.
Les appels de fonction (selon la plate-forme) impliquent généralement quelques 10s d'instructions, y compris l'enregistrement / la restauration de la pile. Certains appels de fonction consistent en une instruction de saut et de retour.
Mais il y a d'autres choses qui peuvent affecter les performances des appels de fonction. La fonction appelée peut ne pas être chargée dans le cache du processeur, provoquant un échec de cache et forçant le contrôleur de mémoire à saisir la fonction de la RAM principale. Cela peut provoquer un gros coup pour les performances.
En bref: les appels de fonction peuvent ou non avoir un impact sur les performances. La seule façon de le savoir est de profiler votre code. N'essayez pas de deviner où sont les taches de code lentes, car le compilateur et le matériel ont des trucs incroyables dans leurs manches. Profilez le code pour obtenir l'emplacement des zones lentes.
la source
Ceci est une question d'implémentation du compilateur ou du runtime (et de ses options) et ne peut être dit avec certitude.
Dans C et C ++, certains compilateurs insèrent des appels en fonction des paramètres d'optimisation - cela peut être vu de manière triviale en examinant l'assembly généré lors de la consultation d'outils tels que https://gcc.godbolt.org/
D'autres langages, tels que Java, font cela dans le cadre de l'exécution. Cela fait partie du JIT et est développé dans cette question SO . En particulier, regardez les options JVM pour HotSpot
Alors oui, le compilateur HotSpot JIT insérera des méthodes qui répondent à certains critères.
L' impact de cela est difficile à déterminer car chaque machine virtuelle Java (ou compilateur) peut faire les choses différemment et essayer de répondre avec le trait large d'un langage est presque certainement une erreur. L'impact ne peut être correctement déterminé qu'en profilant le code dans l'environnement d'exécution approprié et en examinant la sortie compilée.
Cela peut être vu comme une approche erronée avec CPython non en ligne, mais Jython (Python fonctionnant dans la JVM) ayant certains appels en ligne. De même, MRI Ruby ne sera pas en ligne alors que JRuby le ferait, et ruby2c qui est un transpileur pour ruby en C ... qui pourrait alors être en ligne ou non en fonction des options du compilateur C qui ont été compilées.
Les langues ne s'alignent pas. Les implémentations peuvent .
la source
Vous recherchez des performances au mauvais endroit. Le problème avec les appels de fonction n'est pas qu'ils coûtent cher. Il y a un autre problème. Les appels de fonction pourraient être absolument gratuits et vous auriez toujours cet autre problème.
C'est qu'une fonction est comme une carte de crédit. Comme vous pouvez facilement l'utiliser, vous avez tendance à l'utiliser plus que vous ne le devriez. Supposons que vous l'appeliez 20% de plus que nécessaire. Ensuite, un gros logiciel typique contient plusieurs couches, chacune appelant des fonctions dans la couche ci-dessous, de sorte que le facteur 1,2 peut être aggravé par le nombre de couches. (Par exemple, s'il y a cinq couches et que chaque couche a un facteur de ralentissement de 1,2, le facteur de ralentissement composé est de 1,2 ^ 5 ou 2,5.) Ce n'est qu'une façon de penser.
Cela ne signifie pas que vous devez éviter les appels de fonction. Cela signifie que lorsque le code est opérationnel, vous devez savoir comment trouver et éliminer les déchets. Il existe de très bons conseils sur la façon de procéder sur les sites stackexchange. Cela donne une de mes contributions.
AJOUTÉ: Petit exemple. Une fois, j'ai travaillé dans une équipe sur un logiciel d'usine qui suivait une série de bons de travail ou "travaux". Il y avait une fonction
JobDone(idJob)
qui pouvait dire si un travail était fait. Un travail a été fait lorsque toutes ses sous-tâches ont été effectuées, et chacune de ces tâches a été effectuée lorsque toutes ses sous-opérations ont été effectuées. Toutes ces choses ont été enregistrées dans une base de données relationnelle. Un seul appel à une autre fonction pourrait extraire toutes ces informations,JobDone
appelées cette autre fonction, voir si le travail était terminé et jeter le reste. Ensuite, les gens pourraient facilement écrire du code comme celui-ci:ou
Vous voyez le point? La fonction était si "puissante" et facile à appeler qu'elle a été trop appelée. Le problème de performances n'était donc pas les instructions d'entrée et de sortie de la fonction. C'était qu'il devait y avoir un moyen plus direct de savoir si des travaux étaient effectués. Encore une fois, ce code aurait pu être intégré à des milliers de lignes de code par ailleurs innocent. Essayer de le réparer à l'avance est ce que tout le monde essaie de faire, mais c'est comme essayer de lancer des fléchettes dans une pièce sombre. Ce dont vous avez besoin à la place, c'est de le faire fonctionner, puis laissez le "code lent" vous dire ce que c'est, simplement en prenant du temps. Pour cela, j'utilise une pause aléatoire .
la source
Je pense que cela dépend vraiment de la langue et de la fonction. Alors que les compilateurs c et c ++ peuvent incorporer de nombreuses fonctions, ce n'est pas le cas pour Python ou Java.
Bien que je ne connaisse pas les détails spécifiques à Java (sauf que chaque méthode est virtuelle mais je vous suggère de mieux vérifier la documentation), en Python, je suis sûr qu'il n'y a pas d'inline, aucune optimisation de récursivité de queue et les appels de fonction sont assez chers.
Les fonctions Python sont essentiellement des objets exécutables (et en fait, vous pouvez également définir la méthode call () pour faire d'une instance d'objet une fonction). Cela signifie qu'il y a beaucoup de frais généraux pour les appeler ...
MAIS
lorsque vous définissez des variables à l'intérieur des fonctions, l'interpréteur utilise LOADFAST au lieu de l'instruction LOAD normale dans le bytecode, ce qui rend votre code plus rapide ...
Une autre chose est que lorsque vous définissez un objet appelable, des modèles comme la mémorisation sont possibles et ils peuvent effectivement accélérer considérablement votre calcul (au prix d'utiliser plus de mémoire). Fondamentalement, c'est toujours un compromis. Le coût des appels de fonction dépend également des paramètres, car ils déterminent la quantité de choses que vous devez réellement copier sur la pile (donc en c / c ++, il est courant de passer de gros paramètres comme des structures par des pointeurs / référence plutôt que par valeur).
Je pense que votre question est en pratique trop large pour recevoir une réponse complète sur stackexchange.
Ce que je vous suggère de faire est de commencer avec une langue et d'étudier la documentation avancée pour comprendre comment les appels de fonction sont implémentés par cette langue spécifique.
Vous serez surpris par le nombre de choses que vous apprendrez dans ce processus.
Si vous avez un problème spécifique, faites des mesures / profilage et décidez de la météo, il est préférable de créer une fonction ou de copier / coller le code équivalent.
si vous posez une question plus précise, il serait plus facile d'obtenir une réponse plus précise, je pense.
la source
Il y a quelque temps, j'ai mesuré les frais généraux des appels de fonction C ++ directs et virtuels sur le Xenon PowerPC .
Les fonctions en question avaient un seul paramètre et un seul retour, donc le passage des paramètres s'est produit sur les registres.
Pour faire court, la surcharge d'un appel de fonction direct (non virtuel) était d'environ 5,5 nanosecondes, ou 18 cycles d'horloge, par rapport à un appel de fonction en ligne. La surcharge d'un appel de fonction virtuelle était de 13,2 nanosecondes, ou 42 cycles d'horloge, par rapport à l'inline.
Ces synchronisations sont probablement différentes selon les différentes familles de processeurs. Mon code de test est ici ; vous pouvez exécuter la même expérience sur votre matériel. Utilisez un minuteur de haute précision comme rdtsc pour votre implémentation CFastTimer; l'heure système () n'est pas assez précise.
la source