Quels compilateurs C ++, le cas échéant, effectuent une optimisation de la récursivité de la queue?

150

Il me semble que cela fonctionnerait parfaitement pour faire une optimisation de la récursivité de queue en C et C ++, mais pendant le débogage, je ne semble jamais voir une pile de cadres indiquant cette optimisation. C'est plutôt bien, car la pile me dit à quel point la récursivité est profonde. Cependant, l'optimisation serait également très intéressante.

Est-ce que des compilateurs C ++ font cette optimisation? Pourquoi? Pourquoi pas?

Comment dire au compilateur de le faire?

  • Pour MSVC: /O2ou/Ox
  • Pour GCC: -O2ou-O3

Que diriez-vous de vérifier si le compilateur a fait cela dans un certain cas?

  • Pour MSVC, activez la sortie PDB pour pouvoir tracer le code, puis inspectez le code
  • Pour GCC ..?

Je prendrais toujours des suggestions sur la façon de déterminer si une certaine fonction est optimisée comme celle-ci par le compilateur (même si je trouve rassurant que Konrad me dise de l'assumer)

Il est toujours possible de vérifier si le compilateur fait cela du tout en faisant une récursion infinie et en vérifiant si cela se traduit par une boucle infinie ou un débordement de pile (je l'ai fait avec GCC et j'ai découvert que c'était -O2suffisant), mais je veux être capable de vérifier une certaine fonction dont je sais qu'elle se terminera de toute façon. J'adorerais avoir un moyen facile de vérifier cela :)


Après quelques tests, j'ai découvert que les destructeurs ruinaient la possibilité de faire cette optimisation. Cela peut parfois valoir la peine de changer la portée de certaines variables et temporaires pour s'assurer qu'elles sortent de la portée avant le début de l'instruction return.

Si un destructeur doit être exécuté après l'appel final, l'optimisation de l'appel final ne peut pas être effectuée.

Magnus Hoff
la source

Réponses:

129

Tous les compilateurs grand public actuels effectuent assez bien l' optimisation des appels de queue (et ce depuis plus d'une décennie), même pour des appels mutuellement récursifs tels que:

int bar(int, int);

int foo(int n, int acc) {
    return (n == 0) ? acc : bar(n - 1, acc + 2);
}

int bar(int n, int acc) {
    return (n == 0) ? acc : foo(n - 1, acc + 1);
}

Laisser le compilateur faire l'optimisation est simple: activez simplement l'optimisation pour la vitesse:

  • Pour MSVC, utilisez /O2ou /Ox.
  • Pour GCC, Clang et ICC, utilisez -O3

Un moyen simple de vérifier si le compilateur a effectué l'optimisation consiste à effectuer un appel qui entraînerait autrement un débordement de pile - ou à consulter la sortie de l'assembly.

Comme note historique intéressante, l'optimisation des appels de queue pour C a été ajoutée au GCC dans le cadre d'une thèse de diplôme par Mark Probst. La thèse décrit quelques mises en garde intéressantes dans la mise en œuvre. Cela vaut la peine d'être lu.

Konrad Rudolph
la source
ICC le ferait, je crois. À ma connaissance, ICC produit le code le plus rapide du marché.
Paul Nathan
35
@Paul La question est de savoir dans quelle mesure la vitesse du code ICC est causée par les optimisations algorithmiques telles que les optimisations des appels de queue et dans quelle mesure les optimisations du cache et de la micro-instruction peuvent être réalisées par Intel, avec sa connaissance intime de ses propres processeurs.
Imagist
6
gcca une option plus étroite -foptimize-sibling-callspour "optimiser les appels récursifs frères et sœurs". Cette option (selon les gcc(1)pages de manuel pour les versions 4.4, 4.7 et 4.8 ciblant différentes plates - formes) est activé au niveau -O2, -O3, -Os.
FooF
De plus, exécuter en mode DEBUG sans demander explicitement des optimisations ne fera AUCUNE optimisation. Vous pouvez activer PDB pour le vrai mode de publication EXE et essayer de passer par là, mais notez que le débogage en mode Release a ses complications - variables invisibles / supprimées, variables fusionnées, variables devenant hors de portée dans une portée inconnue / inattendue, variables qui ne rentrent jamais portée et sont devenues de vraies constantes avec des adresses au niveau de la pile, et - enfin - des cadres de pile fusionnés ou manquants. Habituellement, les cadres de pile fusionnés signifient que l'appelé est en ligne et que les cadres manquants / fusionnés en arrière sont probablement un appel de queue.
Петър Петров
21

gcc 4.3.2 intègre complètement cette fonction ( atoi()implémentation merdique / triviale ) dans main(). Le niveau d'optimisation est -O1. Je remarque que si je joue avec (même en le changeant de staticà extern, la récursivité de la queue disparaît assez rapidement, donc je ne compterais pas dessus pour l'exactitude du programme.

#include <stdio.h>
static int atoi(const char *str, int n)
{
    if (str == 0 || *str == 0)
        return n;
    return atoi(str+1, n*10 + *str-'0');
}
int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i)
        printf("%s -> %d\n", argv[i], atoi(argv[i], 0));
    return 0;
}
Tom Barta
la source
1
Cependant, vous pouvez activer l'optimisation du temps de liaison et je suppose que même une externméthode pourrait alors être intégrée.
Konrad Rudolph
5
Étrange. Je viens de tester gcc 4.2.3 (x86, Slackware 12.1) et gcc 4.6.2 (AMD64, Debian Wheezy) et avec-O1 il n'y a pas inline et aucune optimisation queue récursivité . Vous devez utiliser -O2pour cela (enfin, dans 4.2.x, qui est plutôt ancien maintenant, il ne sera toujours pas intégré). BTW Il vaut également la peine d'ajouter que gcc peut optimiser la récursivité même si ce n'est pas strictement une queue (comme factorielle sans accumulateur).
przemoc
16

En plus de l'évidence (les compilateurs ne font pas ce genre d'optimisation à moins que vous ne le demandiez), il y a une complexité à propos de l'optimisation des appels de fin en C ++: les destructeurs.

Compte tenu de quelque chose comme:

   int fn(int j, int i)
   {
      if (i <= 0) return j;
      Funky cls(j,i);
      return fn(j, i-1);
   }

Le compilateur ne peut pas (en général) l'optimisation de l'appel de fin car il doit appeler le destructeur de cls après le retour de l'appel récursif.

Parfois, le compilateur peut voir que le destructeur n'a pas d'effets secondaires visibles de l'extérieur (donc cela peut être fait tôt), mais souvent il ne peut pas.

Une forme particulièrement courante de ceci est où Funkyest en fait un std::vectorou similaire.

Martin Bonner soutient Monica
la source
Ça ne marche pas pour moi. Les systèmes m'indiquent que mon vote est verrouillé jusqu'à ce que la réponse soit modifiée.
hmuelner
Je viens de modifier la réponse (les paranthèses supprimées) et maintenant je peux annuler mon vote défavorable.
hmuelner
11

La plupart des compilateurs ne font aucun type d'optimisation dans une version de débogage.

Si vous utilisez VC, essayez une version de version avec les informations PDB activées - cela vous permettra de suivre l'application optimisée et vous devriez, espérons-le, voir ce que vous voulez alors. Notez, cependant, que le débogage et le traçage d'une version optimisée vous feront voyager partout, et souvent vous ne pouvez pas inspecter directement les variables car elles ne finissent que dans des registres ou sont entièrement optimisées. C'est une expérience "intéressante" ...

Greg Whitfield
la source
2
essayez gcc why -g -O3 et pour obtenir des opimisations dans une version de débogage. xlC a le même comportement.
g24l du
Quand vous dites «la plupart des compilateurs»: quelles collections de compilateurs considérez-vous? Comme indiqué, il y a au moins deux compilateurs qui effectuent des optimisations pendant la compilation du débogage - et pour autant que je sache, VC le fait aussi (sauf si vous activez peut-être modifier et continuer).
skyking
7

Comme Greg le mentionne, les compilateurs ne le feront pas en mode débogage. Il est normal que les versions de débogage soient plus lentes qu'une génération de prod, mais elles ne devraient pas planter plus souvent: et si vous dépendez d'une optimisation des appels de fin, ils peuvent faire exactement cela. Pour cette raison, il est souvent préférable de réécrire l'appel de fin comme une boucle normale. :-(

0124816
la source