Pourquoi ne pas marquer tout en ligne?

Tout d’abord, je ne cherche pas à forcer le compilateur à intégrer l’implémentation de chaque fonction.

Pour réduire le niveau de réponses erronées, assurez-vous de comprendre ce que signifie réellement le mot-clé inline . Voici une bonne description, inline vs static vs extern .

Donc, ma question, pourquoi ne pas marquer chaque définition de fonction en inline ? Par exemple, idéalement, la seule unité de compilation serait main.cpp . Ou peut-être quelques autres pour les fonctions qui ne peuvent pas être définies dans un fichier d’en-tête (idem de pimpl, etc.).

La théorie derrière cette demande étrange est que cela donnerait à l’optimiseur le maximum d’informations avec lesquelles travailler. Cela pourrait bien entendu intégrer des implémentations de fonctions, mais cela pourrait aussi faire de l’optimisation “cross-module” car il n’y a qu’un seul module. Y a-t-il d’autres avantages?

Quelqu’un at-il essayé cela avec une application réelle? La performance a-t-elle augmenté? diminution?!?

Quels sont les inconvénients du marquage en ligne de toutes les définitions de fonctions?

  • La compilation peut être plus lente et consumr beaucoup plus de mémoire.
  • Les versions itératives sont endommagées, l’application entière devra être reconstruite après chaque modification.
  • Les temps de liaison peuvent être astronomiques

Tous ces inconvénients affectent uniquement le développeur. Quels sont les inconvénients d’exécution?

Vouliez-vous vraiment dire #include tout? Cela ne vous donnerait qu’un seul module et laisserait l’optimiseur voir tout le programme en même temps.

En fait, Microsoft Visual C ++ fait exactement cela lorsque vous utilisez le commutateur /GL (Whole Program Optimization) , il ne comstack en fait rien jusqu’à ce que l’éditeur de liens s’exécute et ait access à tout le code. D’autres compilateurs ont des options similaires.

sqlite utilise cette idée. Au cours du développement, il utilise une structure source traditionnelle. Mais pour une utilisation réelle, il existe un fichier c énorme (112k lignes). Ils le font pour une optimisation maximale. Revendiquer une amélioration de la performance d’environ 5 à 10%

http://www.sqlite.org/amalgamation.html

Nous (et quelques autres sociétés de jeux) avons essayé de le faire en créant un seul UPC-CP qui incluait tous les autres; c’est une technique connue. Dans notre cas, cela ne semblait pas avoir beaucoup d’incidence sur l’exécution, mais les inconvénients de la compilation que vous avez mentionnés se sont avérés totalement invalidants. Avec une demi-heure de compilation après chaque changement, il devient impossible de procéder à une itération efficace. (Et c’est avec l’application divisée dans plus d’une douzaine de bibliothèques différentes.)

Nous avons essayé de créer une configuration différente de telle sorte que nous ayons plusieurs fichiers .obj lors du débogage et que nous ayons alors le module uber-CPP uniquement dans les versions release-opt, mais nous avons ensuite rencontré le problème de la mémoire. Pour une application suffisamment grande, les outils ne suffisent pas à comstackr un fichier cpp de plusieurs millions de lignes.

Nous avons également essayé LTCG, ce qui a fourni un petit coup de pouce, mais agréable, dans les rares cas où il ne s’est pas arrêté pendant la phase de liaison.

Ceci est semi-lié, mais notez que Visual C ++ peut effectuer une optimisation inter-modules, y compris en ligne entre les modules. Voir http://msdn.microsoft.com/en-us/library/0zza0de8%28VS.80%29.aspx pour plus d’informations.

Pour append une réponse à votre question initiale, je ne pense pas qu’il y aurait un inconvénient au moment de l’exécution, en supposant que l’optimiseur était suffisamment intelligent (d’où pourquoi il a été ajouté en tant qu’option d’optimisation dans Visual Studio). Utilisez simplement un compilateur assez intelligent pour le faire automatiquement, sans créer tous les problèmes que vous mentionnez. 🙂

Question interessante! Vous avez certainement raison de dire que tous les inconvénients énumérés sont spécifiques au développeur. Je dirais toutefois qu’un développeur défavorisé est beaucoup moins susceptible de produire un produit de qualité. Il peut ne pas y avoir d’inconvénients à l’exécution, mais imaginez la réticence d’un développeur à apporter de petites modifications si chaque compilation prend des heures (voire des jours).

Je regarderais cela d’un angle “d’optimisation prématuré”: le code modulaire dans plusieurs fichiers facilite la vie du programmeur, il y a donc un avantage évident à faire les choses de cette façon. Ce n’est que si une application spécifique s’avère trop lente et que l’on peut démontrer que tout intégrer apporte une amélioration mesurable. Envisagerais-je même de gêner les développeurs? Même à ce moment-là, ce serait après qu’une grande partie du développement aurait été réalisée (pour pouvoir être mesurée) et ne serait probablement réalisée que pour les versions de production.

Cela se fait déjà dans certains cas. Il est très similaire à l’idée de construction d’unité , et les avantages et les inconvénients ne sont pas de ce que vous avez décrit:

  • plus de potentiel pour le compilateur à optimiser
  • le temps de liaison disparaît fondamentalement (si tout est dans une seule unité de traduction, il n’y a rien à lier, vraiment)
  • le temps de compilation va bien, d’une manière ou d’une autre. Les constructions incrémentielles deviennent impossibles, comme vous l’avez mentionné. D’un autre côté, une version complète sera plus rapide qu’elle ne le serait autrement (car chaque ligne de code est compilée une seule fois exactement. Dans une version standard, le code dans les en-têtes est compilé dans chaque unité de traduction où l’en-tête est inclus) )

Mais dans les cas où vous avez déjà beaucoup de code d’en-tête uniquement (par exemple, si vous utilisez beaucoup de Boost), l’optimisation peut être très utile, à la fois en termes de temps de construction et de performances exécutables.

Comme toujours, lorsque la performance est en jeu, cela dépend. Ce n’est pas une mauvaise idée, mais ce n’est pas universellement applicable non plus.

En ce qui concerne le temps, vous avez essentiellement deux façons de l’optimiser:

  • minimiser le nombre d’unités de traduction (pour que vos en-têtes soient inclus dans un nombre plus restreint), ou
  • minimiser la quantité de code dans les en-têtes (de sorte que le coût d’inclusion d’un en-tête dans plusieurs unités de traduction diminue)

Le code C prend généralement la deuxième option, à son extrême: presque rien en dehors des déclarations directes et des macros ne sont conservés dans les en-têtes. C ++ se situe souvent à mi-chemin, où vous obtenez le pire temps de compilation possible (mais les PCH et / ou les versions incrémentielles peuvent vous faire gagner du temps), mais en allant plus loin. vraiment faire des merveilles pour le temps de construction total.

Peu d’avantages Sur un bon compilateur pour une plateforme moderne, inline n’affectera que très peu de fonctions. Il est juste un indice pour le compilateur, les compilateurs modernes sont assez bons pour prendre cette décision eux-mêmes, et le coût d’un appel de fonction est devenu assez petit (souvent, le principal avantage de l’inline n’est pas de réduire optimisations supplémentaires).

Comstackr le temps Cependant, comme inline change également la sémantique, vous devrez #include tout dans une énorme unité de compilation. Cela augmente généralement le temps de compilation de manière significative, ce qui est fatal pour les grands projets.

Taille du code
Si vous vous éloignez des plates-formes de bureau actuelles et de ses compilateurs haute performance, les choses changent beaucoup. Dans ce cas, l’augmentation de la taille du code générée par un compilateur moins intelligent posera un problème, à tel point que le code sera considérablement plus lent. Sur les plates-formes embarquées, la taille du code est généralement la première ressortingction.

Cependant, certains projets peuvent et profitent de tout. Cela vous donne le même effet que l’optimisation du temps de liaison, du moins si votre compilateur ne suit pas aveuglément la inline .

C’est à peu près la philosophie qui sous-tend l’optimisation des programmes complets et la génération de codes temporels de liaison (LTCG): les meilleures possibilités d’optimisation sont les connaissances globales.

D’un sharepoint vue pratique, c’est un peu pénible, car chaque modification que vous apportez nécessite désormais une recompilation de l’intégralité de votre source. En règle générale, vous avez besoin d’une version optimisée moins fréquemment que nécessaire pour apporter des modifications arbitraires.

Je l’ai essayé à l’ère Metrowerks (c’est assez facile à configurer avec une construction de style “Unity”) et la compilation n’a jamais été terminée. Je le mentionne seulement pour souligner que c’est une configuration de stream de travail susceptible d’imposer la chaîne d’outils de manière non anticipée.

L’hypothèse est que le compilateur ne peut pas optimiser les fonctions. C’est une limitation de compilateurs spécifiques et non un problème général. Utiliser ceci comme solution générale pour un problème spécifique peut être mauvais. Le compilateur peut très bien simplement gonfler votre programme avec ce qui aurait pu être des fonctions réutilisables à la même adresse mémoire (pour pouvoir utiliser le cache) en cours de compilation ailleurs (et perdre des performances à cause du cache).

Grandes fonctions en général sur l’optimisation, il existe un équilibre entre la surcharge des variables locales et la quantité de code dans la fonction. Conserver le nombre de variables de la fonction (passées, locales et globales) à l’intérieur du nombre de variables disponibles pour la plate-forme permet à la plupart de restr dans les registres et de ne pas devoir être expulsé, également une stack la trame n’est pas requirejse (dépend de la cible), donc la surcharge de l’appel de fonction est sensiblement réduite. Difficile à faire dans les applications du monde réel tout le temps, mais l’alternative à un petit nombre de grandes fonctions avec beaucoup de variables locales, le code va passer beaucoup de temps à expulser et charger des registres avec des variables vers / depuis cible).

Essayez llvm, il peut optimiser sur tout le programme, pas seulement fonction par fonction. La version 27 avait rattrapé l’optimiseur de gcc, du moins pour un test ou deux, je n’ai pas effectué de tests de performance exhaustifs. Et 28 est sorti alors je suppose que c’est mieux. Même avec quelques fichiers, le nombre de combinaisons de boutons de réglage est trop important. Je trouve préférable de ne pas optimiser le tout jusqu’à ce que vous ayez tout le programme dans un seul fichier, puis effectuez votre optimisation, en donnant à l’optimiseur le programme complet pour travailler, essentiellement ce que vous essayez d’inline, mais sans les bagages.

Supposons que foo() et bar() appellent tous deux un helper() . Si tout est dans une unité de compilation, le compilateur peut choisir de ne pas incorporer helper() , afin de réduire la taille totale des instructions. Cela fait que foo() effectue un appel de fonction non incorporé à helper() .

Le compilateur ne sait pas qu’une amélioration de la nanoseconde de la durée d’exécution de foo() ajoute 100 $ / jour à vos attentes. Il ne sait pas qu’une amélioration des performances ou une dégradation de quelque chose en dehors de foo() n’a aucun impact sur vos résultats.

Seul vous, en tant que programmeur, savez ces choses (après un profilage et une parsing soignés bien sûr). La décision de ne pas incorporer bar() est un moyen de dire au compilateur ce que vous savez.

Le problème avec l’inlining est que vous voulez que les fonctions hautes performances tiennent dans le cache. Vous pensez peut-être que la surcharge des appels de fonctions est la plus importante, mais dans de nombreuses architectures, une absence de cache fera sauter le couple et le fera sortir de l’eau. Par exemple, si vous avez une fonction volumineuse (peut-être profonde) qui doit être appelée très rarement depuis votre chemin principal de haute performance, votre boucle principale haute performance peut croître au sharepoint ne plus pouvoir tenir dans L1 icache. Cela ralentira votre code, bien plus que l’appel de fonction occasionnel.