J'ai récemment rencontré beaucoup de fonctions où GCC génère un très mauvais code sur x86. Ils correspondent tous à un modèle de:
if (mutex->type == SIMPLE) { return atomic_swap(&mutex->lock, 1); } else { /* deal with ownership, etc. */ }
5 Réponses :
pour supprimer explicitement l'allusion pour une seule fonction dans GCC, utilisez: p> Voir aussi fonctionne comme celle-ci sera régulièrement inlinée automatiquement, sauf compilation -O0 (désactiver l'optimisation). P> in c ++, vous pouvez indiquer le compilateur à l'aide du mot clé en ligne p> si Le compilateur ne prendra pas votre indice que vous utilisez probablement trop de registres / branches à l'intérieur de la fonction. La situation est presque certainement résolue en extrayant le bloc "compliqué" dans sa propre fonction. P> Mise à jour strong> J'ai remarqué que vous avez ajouté le fait qu'ils sont des symboles externes. (Veuillez mettre à jour la question avec cette information cruciale). Eh bien, dans un sens, avec des fonctions externes, tous les paris sont éteints. Je ne peux pas vraiment croire que le GCC va par définition en ligne toute une fonction complexe dans un petit appelant simplement em> car il n'est appelé que de là. Peut-être que vous pouvez donner un exemple de code qui démontre le comportement et nous pouvons trouver les drapeaux d'optimisation appropriés pour remédier à ce que? P> aussi strong>, est-ce C ou C ++? En C ++, je sais que c'est un lieu commun pour inclure les fonctions de décision triviale en ligne (principalement comme des membres définis dans la déclaration de classe). Cela ne donnera pas un conflit de liaison comme avec des fonctions simples (externe) C. p> Vous pouvez également avoir des fonctions de modèle définies parfaitement parfaitement dans tous les modules de compilation sans entraîner des conflits de liaison. P> J'espère que vous utilisez C ++ car il vous donnera une tonne d'options ici. p> p>
Non, ils ne le feront pas, à cause de la partie complexe.
Et parce qu'ils sont externes.
Notez également que même si je déplace la branche «compliquée» dans sa propre fonction, (1) GCC le déplacera immédiatement, car GCC s'inscrit uniquement des fonctions qui ne sont appelées qu'à partir d'un endroit et (2) même si GCC ne l'a pas déplacée. Retour, la mise en place d'un appel de la fonction est l'une des causes du code de prologue laid qui rend le cas trivial lent et que GCC ne mettra pas la configuration dans l'une des branches.
@R .. Tu ne nous encourage pas vraiment à vous aider et à penser. Veuillez fournir un exemple de code afin que nous puissions faire une analyse réelle au lieu de faire un «je pense» / «Je suppose» / 'Nah, il serait juste de montrer. Je commence à penser que vous évitiez vraiment une opinion au lieu de poser une question
Ps. Je n'ai remarqué que maintenant que vous mentionnez la fonction étant externe. C'est une douleur. Mise à jour de ma réponse
Sur les fonctions d'inlinage que l'appelle une seule fois, voir -Finline-fonction-appelé-une fois code> à
J'ai essayé cela, et cela évite d'économiser les quatre des quatre d'EBX / ESI / EDI / EBP dans le prologue, mais il aligne toujours la pile avant le conditionnel. -MPReferred-Stack-Boundary = 2 code> corrige cela mais c'est un piratage laid. Ce qui est vraiment stupide ... GCC optimise l'appel de la fonction à l'affaire complexe dans un appel de la queue (juste un JMP) afin qu'il doit annuler le réglage de la pile avant le JMP!
Existe-t-il un moyen de spécifier la fonction par fonction (avec des attibutes) qu'elle ne se soucie pas si la pile est mal alignée? Si tel est le cas, il serait possible de faire une macro d'attribut unique qui inhiberait la stupidité de la GCC et la cache-elle du code réel ... La seule laideur dans le code diviserait l'affaire complexe dans une fonction distincte mais c'est pas si mal.
Il me semble que attribut ((optimiser ("xxx"))) code> ne prend en charge que
-o code> niveaux et
-f code> options, pas
-M CODE> Options ... Je suppose que je pourrais faire appel à l'appel malaligné avec ASM, mais cela se mettra à des hacks vraiment horribles ...
Je refralerais probablement le code pour encourager l'affranchissement du cas simple. Cela dit, vous pouvez utiliser Vous ne serez probablement pas en mesure d'obtenir beaucoup d'options de compilation, cependant, et devrez refroidir. P> -finline-limit code> pour créer
gcc code> envisager d'inclure des fonctions plus grandes ou
-fomit-image-pointeur -fno-exceptions code> minimiser le cadre de la pile. (Notez que ce dernier peut casser le débogage et la cause des exceptions C ++ pour mal se conduire mal.) P>
WOW, j'ai eu beaucoup de réponses suggérant de faire des choses pour que la GCC soit meilleure en ligne, lorsque la question indique que cette fonction externe et une inlinage ne sont pas possibles ...
L'inlinçon dont je parle est, dans le cas des externes, des fonctions d'emballage ou même des macros. Attrapez le (s) cas (s) simple (s) en ligne et invoquez l'extérieur pour le reste. Cela implique de tester la condition deux fois, mais il ne s'agit que d'un cas cher de toute façon ce qui est donc en bas dans le bruit. Ce n'est pas la science de la fusée.
Encore une fois, ce n'est pas possible dans le code de la bibliothèque, à moins que vous ne souhaitiez écrire un de ces bibliothèques i> b> où chaque version est incompatible car tous les internes de structures de bibliothèque supposées-opaques a été inlidé dans l'application appelante. Si GCC ne gâche pas les cas simples, mes fonctions externes seraient de 80 à 90% aussi bonnes que des macros ou des fonctions en ligne, mais elles ne sont que de 50 à 70% seulement, car GCC ajoute beaucoup de frais généraux inutiles.
Vous construisez activement une contradiction ici. S'il est possible de modifier la fonction de fonction, il est possible d'interposer une macro. Si vous ne pouvez pas interposer une macro, il est trop tard pour changer la fonction de fonction.
Je n'essaie pas d'utiliser des macros ou des fonctions en ligne, du tout. J'essaie d'écrire une fonction normale externe code> -LIkage dans une bibliothèque disposant d'un "chemin hot-chemin" court et rapide (grâce à Jens pour le mot) avec une entrée / sortie minimale et un plus grand branche qui n'est exécutée qu'une seule fois ou rarement.
Et c'est la mauvaise façon de le faire. Le refactoring est la bonne façon de le faire. Sinon ... Eh bien, si vous essayez de l'écrire, le moyen d'écrire est un petit talon d'ASM code> ASM code> qui invoque une fonction complète plus grande si nécessaire - ce qui est exactement le même refactoring fait le dur façon.
Avez-vous lu les commentaires sur les autres messages? Le refactoring aide certains i> mais pas beaucoup. Par défaut, GCC s'inscrit dans le chemin complexe de factorard-out de la part, si c'est Linkage statique code>. Cela peut être inhibé en le faisant
externe code> ou en utilisant
-fno-inline-fonctions-appelé-une fois code>, puis le code généré est pas horrible i >, sauf qu'il a toujours du prologue / épilogue d'alignement de la pile à 16 octets, car il est pourrait i> faire un appel de fonction. Désactivation de celui avec
-MPreferred-Stack-Stack-Boundary = 2 code> donne le code optimal correct, mais je cherche quelque chose de moins hackish ...
J'ai vu ça, oui. Mais si le refactoring ne vous aide pas, alors votre lien magique ne sera ni votre traction magique, sinon vous le faites mal. L'utilisation d'une macro pour le chemin chaud doit impliquer zéro i> liaison.
Les macros ne sont pas une option. Idéalement, il y aurait une option de gcc -fhot-chemins code> activé par défaut dans tous les niveaux
-O code> -o code> permettant d'identifier les chemins de branche via une fonction impliquant aucun appels de fonction et suffisamment bas Enregistrer la pression dont aucun registre ne doit être enregistré sur la pile et GCC déplacerait les frais généraux à l'intérieur des autres branches. Mais on dirait que je suis coincé avec des emballages d'ASM, une pile de hacks Option de GCC Hacks et une factorisation spécifique à l'optimiseur arbitraire, ou de manière modérément lente ...
"Pas une option"? Vous construisez toujours une tour de contraintes non-sens. Si vous insistez I> pour rendre la vie difficile pour vous-même, alors sûre: la vie est difficile.
Les macros ne sont pas une option pour une bibliothèque partagée. Ce n'est pas quelque chose que j'ai composé. C'est fondamental. Laisser une macro est inlinée dans l'application i> signifie que deux organes de code qui doivent convenir de la définition et de la sémantique des structures de données impliquées: la macro (statique) liée à l'application et le code dans la bibliothèque (partagée). Maintenant, soit vous devez verrouiller votre bibliothèque pour toujours dans la même mise en œuvre des structures de données (qui blesse plus d'une mauvaise fonction prologue) ou de réduire la compatibilité et forcez tout le monde à transporter plusieurs versions de bibliothèques.
Si vous fournissez une bibliothèque partagée sans en-tête, racontez le compilateur, les bons types, etc., vous faites déjà une erreur fondamentale. Une fois que vous avez fourni cet en-tête, des macros enroulées sont gratuites.
Je pense que vous avez un malentendu grave ici. Il y a une grande différence entre un en-tête qui fournit des prototypes pour interfaces i> conçus pour être une partie permanente de l'API (et ABI) pour la bibliothèque et un en-tête qui fournit demi-implémentations < / I> avec des dépendances sur les internes des structures de données opaques de la bibliothèque. Notez que dans une bonne bibliothèque, les définitions code> struct code> ne seront même pas dans l'en-tête public. Ils seront tous des types incomplets avec des définitions d'une en-tête interne de bibliothèque qui ne sont pas installées.
Je pense que votre malentendu est que vous devez cacher chaque petite chose. Vous insistez sur le fait que la façon standard de faire cela est fondamentalement fausse de manière inutile. Encore une fois: si vous insistez pour rendre la vie difficile, c'est assez difficile. Arrêtez de construire des barrages routiers artificiels.
Et je pense que vous avez mal compris que je suis prêt à rédiger un code de la CRPA qui présente de graves coûts de maintenance du système permettant de créer les cas d'utilisation les plus intenses de 30% plus rapides et typiques de 10% plus rapidement. Je ne suis pas. Ce type de comportement peut être acceptable si votre bibliothèque fait partie d'un jeu qui nécessite une vitesse maximale, mais ce n'est pas acceptable lorsque vous avez des milliers ou des dizaines de milliers de binaires dynamiques liées à celle-ci (sans parler d'autres bibliothèques partagées).
Une macro pour le chemin rapide est la dernière fois que j'ai vérifié, la meilleure pratique - pas de code de merde. Mais puisque vous avez votre propre monde préféré, allez assembleur de piratage. J'ai fini.
Mise à niveau peut-être votre version de GCC? 4.6 vient d'être libéré. Pour autant que je sache, il a la possibilité de "en ligne partielle". C'est-à-dire qu'une partie extérieure facilement intégrable d'une fonction est inline et la partie coûteuse est transformée en un appel. Mais je dois admettre que je ne l'ai pas essayé moi-même. P>
EDIT: STRUT> La déclaration que je faisais allusion à partir du changelog: p>
Inlinge partielle est maintenant pris en charge et
Activé par défaut à -O2 et plus grand.
La fonctionnalité peut être contrôlée via
-fertial-inlindication. P>
Fonctions d'inlinisation partielle des fonctions avec
court chemin à chaud pour revenir. Ceci permet
Inlinaison plus agressive du chaud
chemin ... p>
Inlinaison lors de l'optimisation de la taille
(soit dans les régions froides d'un programme
ou lors de la compilation de -o) était
amélioré pour mieux gérer les programmes C ++
avec une pénalité d'abstraction plus grande,
conduisant à un code plus petit et plus rapide. P>
blockQuote>
@Jens: Merci pour l'idée, mais l'inlinage n'est pas une option. Ceci est une fonction externe dans une bibliothèque et la moitié d'elle est inlinée dans l'appelant créerait une dépendance de mise en œuvre / version désagréable dans l'appelant.
@R .. Dans tous les cas, inline code> ou non, il semble que les versions plus récentes de GCC puissent ainsi diviser une partie d'une fonction coûteuse. Peut-être en examinant les récentes améliorations d'optimisation de la GCC, vous trouverez quelque chose d'utile.
Y a-t-il une option particulière que je devrais regarder?
Que ("piste chaude" fractionnement) ressemble au type d'optimisation générale dont j'ai besoin, mais pour une utilisation en cas d'inlinage. Je me demande si GCC peut l'appliquer aux fonctions qui ne sont pas candidats à l'inlinisation. Je pourrais avoir 4.6 et essayer de jouer avec elle ... mais je suppose que nous devrons attendre 4,7 ou 4,8 avant que cette fonctionnalité ne soit utile au cas que j'ai décrit ... :-(
@R .., vous pouvez toujours compiler une seule fonction par unité de compilation. Dites au compilateur de en ligne code> juste cette fonction dans cette unité mais forcez la génération du symbole, là-bas. Si vos fichiers d'en-tête ne contiennent que des prototypes et aucune définition, le compilateur ne peut tout simplement pas en ligne pour de vrai. Je suppose que GCC construirait la fonction de la fonctionnalité "HOT PATH", le divisant ainsi en deux.
alors prenant cela hors ligne si vous voulez
Il ne semble pas y avoir de solution non piraqueuse à l'heure actuelle, mais votre réponse est la chose la plus proche du droit Direction I> qui pourrait éventuellement conduire à une solution en tant qu'avances de GCC, alors ... acceptées!
Y a-t-il eu des améliorations à ce sujet? Dans GCC ou LLVM?
@Cedomirsegulja, je n'ai pas enquêté que récemment. Ce que j'ai récemment fait et qui semblait fonctionner, c'est mettre le cas complexe (par exemple une telle initialisation ponctuelle) dans une fonction statique code>. Ensuite, GCC est capable de déplacer la poussée et de sauter sur cette fonction ponctuelle et de la fonction d'interface utilisateur réelle agréable et propre.
Voir comme celles-ci sont des appels externes, il pourrait être possible que la GCC les traite comme des registres dangereux et de préservation de l'appel de la fonction (difficile à savoir sans voir les registres qu'il conserve, y compris ceux que vous dites 'ne sont pas utilisés '). Hors de la curiosité, ce registre excessiviste se répandit-il toujours avec toutes les optimisations désactivées? p>
Sur X86 ABI, EBX, EBP, ESI et EDI doivent être enregistrés par la callee si elle les emporte. C'est correct. Le problème est que GCC inconditionnellement fait tout le travail de l'épargne et de les restaurer à l'entrée et à la sortie de la fonction, même lorsque le cas simple ne les emporte jamais. S'il a déplacé l'économie et la restauration à l'intérieur de la branche complexe, la branche simple serait à moitié (ou moins) autant d'instructions et plus rapidement.
@R ..: Vous pouvez modifier un peu ce comportement, mais il n'y a pas beaucoup de contrôle grainé fin - et si ces fonctions sont extern code> et non sous votre contrôle, comment savez-vous qu'il y a des registres inutilisés?
@Geekosaur: Cette question n'a rien à voir avec la génération de code pour l'appelant i>, ce qui ne peut bien sûr pas savoir ce qui enregistre la callee utilise. Tout ce sujet concerne la génération de code pour la callee i>, qui sait absolument ce que les registres utilisent.
Non, ce n'est que absolument i> sait si vous l'écrivez dans l'assembleur ou si vous insistez sur une version de compilation et un niveau d'optimisation particulier afin que vous sachiez avec certitude le code généré. En tout cas, j'ai fini avec cette question. Vous êtes clairement coincé dans une vue mondiale dans laquelle la seule réponse possible est «Vous ne pouvez pas».
Et vous êtes clairement intéressé uniquement à me donner des "conseils" dans le sens de "jeter d'autres considérations beaucoup plus importantes sur la fenêtre et faire ce que je dis" plutôt que d'essayer de répondre à la question.
Je le ferais comme ceci: Le compilateur mon insistent sur l'inlinage complexk_function (), auquel cas vous pouvez utiliser l'attribut NOINLINE. P> P> P> P> >
Juste curieux, que se passe-t-il si vous inverser la déclaration si?
Dans les deux cas, GCC met la fonction Prologue / Epilogue (enregistrement de registres, alignement de la pile, etc.) en dehors des deux cas, le coût est donc engagé sur les deux.
Vos échantillons ne manquent aucun des bits compliquants. Vous suggérez-vous que le compilateur les gâche même avec des blocs vides autres?
OK, ajoutez
printf ("bonjour, world \ n"); code> dans les blocs vides ... Sérieusement, cela n'a pas beaucoup d'importance ce qui est là. Si cela rend un ou plusieurs appels de fonction, vous encourageez un prologue d'alignement de la pile, et s'il utilise une quantité non triviale de registres, vous encourageez d'enregistrer / restaurer un ou plusieurs de EBX / ESI / EDI / EBP.
Notez également: j'ai essayé
__ intégré_expect code> et cela ne fait aucune différence.
Que se passe-t-il si vous déplacez le boîtier complexe en une fonction statique séparée, appelée à partir de la fonction simple?
@CAF: J'ai essayé cela et, avec les paramètres par défaut, GCC énonce simplement la fonction de retour. Si je fais la fonction externe ou utilise
-fno-inline-fonctions-appelé-une fois code> alors le Le code s'améliore mieux, mais puisque le GCC moderne insiste sur la maintenance de la pile 16 octets-alignée sur des appels de fonction, il met le prologue d'alignement de la pile à la place du prologue de sauvegarde de registre, puis annule le prologue immédiatement pour l'optimisation de l'appel de la queue à la fonction complexe. . :-P