Parfois, les compilateurs appellent des fonctions en ligne. Cela signifie qu'ils déplacent le code de la fonction appelée dans la fonction appelante. Cela rend les choses un peu plus rapides car il n'est pas nécessaire de pousser et de faire apparaître des éléments dans et hors de la pile d'appels.
Donc ma question est, pourquoi les compilateurs n'incluent-ils pas tout? Je suppose que cela rendrait l'exécutable beaucoup plus rapide.
La seule raison pour laquelle je peux penser est un exécutable beaucoup plus grand, mais est-ce vraiment important de nos jours avec des centaines de Go de mémoire? L'amélioration des performances n'en vaut-elle pas la peine?
Y a-t-il une autre raison pour laquelle les compilateurs n'incluent pas seulement tous les appels de fonction?
la source
Isn't the improved performance worth it?
Pour une méthode qui exécutera une boucle 100 fois et calculera quelques chiffres sérieux, la surcharge de déplacement de 2 ou 3 arguments vers les registres CPU n'est rien.Réponses:
Notez tout d'abord que l'un des effets majeurs de l'inline est qu'il permet de réaliser de nouvelles optimisations sur le site de l'appel.
Pour votre question: il y a des choses difficiles, voire impossibles à intégrer:
bibliothèques liées dynamiquement
fonctions déterminées dynamiquement (répartition dynamique, appelées via des pointeurs de fonction)
fonctions récursives (la récursion de queue peut)
fonctions pour lesquelles vous n'avez pas le code (mais l'optimisation du temps de liaison le permet pour certaines d'entre elles)
L'intégration n'a pas seulement des effets bénéfiques:
un exécutable plus grand signifie plus d'espace disque et plus de temps de chargement
un exécutable plus grand signifie une augmentation de la pression du cache (notez que l'inclusion de fonctions suffisamment petites telles que de simples getters peut réduire la taille de l'exécutable et la pression du cache)
Et enfin, pour les fonctions qui prennent un temps non trivial à exécuter, le gain ne vaut tout simplement pas la peine.
la source
Le polymorphisme d'exécution est une limitation majeure. S'il y a une répartition dynamique qui se produit lorsque vous écrivez,
foo.bar()
il est impossible d'inline l'appel de méthode. Cela explique pourquoi les compilateurs n'incluent pas tout.Les appels récursifs ne peuvent pas non plus être facilement intégrés.
L'intégration de modules croisés est également difficile à réaliser pour des raisons techniques (une recompilation incrémentielle serait impossible par ex)
Cependant, les compilateurs font beaucoup de choses en ligne.
la source
Premièrement, vous ne pouvez pas toujours être en ligne, par exemple les fonctions récursives peuvent ne pas être toujours en ligne (mais un programme contenant une définition récursive
fact
avec juste une impression defact(8)
pourrait être en ligne).Ensuite, l'inline n'est pas toujours bénéfique. Si le compilateur s'aligne tellement que le code de résultat est assez grand pour que ses parties chaudes ne rentrent pas dans le cache d'instructions L1 par exemple, il pourrait être beaucoup plus lent que la version non intégrée (qui s'adapterait facilement au cache L1) ... De plus, les processeurs récents exécutent très rapidement une
CALL
instruction machine (au moins vers un emplacement connu, c'est-à-dire un appel direct, pas un appel via un pointeur).Enfin, une intégration complète nécessite une analyse complète du programme. Cela pourrait ne pas être possible (ou est trop coûteux). Avec C ou C ++ compilé par GCC (et aussi avec Clang / LLVM ), vous devez activer l' optimisation du temps de liaison (en compilant et en liant avec par exemple
g++ -flto -O2
) et cela prend beaucoup de temps de compilation.la source
Aussi surprenant que cela puisse paraître, le fait de tout inclure ne réduit pas nécessairement le temps d'exécution. La taille accrue de votre code peut rendre difficile pour le CPU de garder tout votre code dans son cache à la fois. Un échec de cache sur votre code devient plus probable et un échec de cache coûte cher. Cela est encore pire si vos fonctions potentiellement intégrées sont volumineuses.
J'ai eu des améliorations de performances notables de temps en temps en retirant de gros morceaux de code marqués comme `` en ligne '' des fichiers d'en-tête, en les plaçant dans le code source, de sorte que le code ne se trouve qu'à un seul endroit plutôt que sur chaque site d'appel. Ensuite, le cache du processeur est mieux utilisé et vous obtenez également un meilleur temps de compilation ...
la source
Tout intégrer ne signifierait pas seulement une augmentation de la consommation de mémoire disque, mais également une augmentation de la consommation de mémoire interne, ce qui n'est pas si abondant. N'oubliez pas que le code repose également en mémoire dans le segment de code; si une fonction est appelée à partir de 10 000 emplacements (par exemple, ceux provenant de bibliothèques standard dans un projet assez important), le code de cette fonction occupe 10 000 fois plus de mémoire interne.
Une autre raison pourrait être les compilateurs JIT; si tout est en ligne, il n'y a pas de points chauds à compiler dynamiquement.
la source
Premièrement, il existe des exemples simples où tout ce qui fonctionne fonctionnera très mal. Considérez ce code C simple:
Devinez ce que tout cela vous fera.
Ensuite, vous faites l'hypothèse que l'inclusion accélérera les choses. C'est parfois le cas, mais pas toujours. L'une des raisons est que le code qui tient dans le cache d'instructions s'exécute beaucoup plus rapidement. Si j'appelle une fonction à partir de 10 emplacements, je vais toujours exécuter du code qui se trouve dans le cache d'instructions. S'il est en ligne, les copies sont partout et s'exécutent beaucoup plus lentement.
Il y a d'autres problèmes: l'inlining produit d'énormes fonctions. Les énormes fonctions sont beaucoup plus difficiles à optimiser. J'ai obtenu des gains considérables de code critique en termes de performances en masquant des fonctions dans un fichier séparé pour empêcher le compilateur de les intégrer. Par conséquent, le code généré pour ces fonctions était bien meilleur lorsqu'elles étaient masquées.
BTW. Je n'ai pas "des centaines de Go de mémoire". Mon ordinateur de travail n'a même pas "des centaines de Go d'espace disque dur". Et si mon application comptait "des centaines de Go de mémoire", cela prendrait 20 minutes juste pour charger l'application en mémoire.
la source