L'optimisation des appels en queue est présente dans de nombreux langages et compilateurs. Dans cette situation, le compilateur reconnaît une fonction de la forme:
int foo(n) {
...
return bar(n);
}
Ici, le langage peut reconnaître que le résultat renvoyé est le résultat d'une autre fonction et transformer un appel de fonction avec un nouveau cadre de pile en saut.
Sachez que la méthode factorielle classique:
int factorial(n) {
if(n == 0) return 1;
if(n == 1) return 1;
return n * factorial(n - 1);
}
n’est pas optimisable en raison de l’inspection nécessaire lors du retour. ( Exemple de code source et de sortie compilée )
Pour rendre cet appel de queue optimisable,
int _fact(int n, int acc) {
if(n == 1) return acc;
return _fact(n - 1, acc * n);
}
int factorial(int n) {
if(n == 0) return 1;
return _fact(n, 1);
}
Compiler ce code avec gcc -O2 -S fact.c
(le -O2 est nécessaire pour permettre l'optimisation dans le compilateur, mais avec plus d'optimisations de -O3, il devient difficile pour un humain de lire ...)
_fact(int, int):
cmpl $1, %edi
movl %esi, %eax
je .L2
.L3:
imull %edi, %eax
subl $1, %edi
cmpl $1, %edi
jne .L3
.L2:
rep ret
( Exemple de code source et de sortie compilée )
On peut voir dans segment .L3
, le jne
plutôt que a call
(qui appelle un sous-programme avec un nouveau cadre de pile).
Veuillez noter que cela a été fait avec C. L’optimisation des appels en Java est difficile et dépend de l’implémentation de la JVM (cela dit, je n’en ai pas vu qui le fasse, car c’est difficile et les implications du modèle de sécurité Java requis nécessitant des cadres de pile - ce que TCO évite) - tail-récursion + java et tail-récursion + optimisation sont de bons ensembles de balises à parcourir. Vous constaterez peut-être que d'autres langages de la machine virtuelle Java sont en mesure d'optimiser davantage la récursion de la queue (essayez clojure (ce qui nécessite l' optimisation de l'appel récurrent à la queue) ou scala).
Cela dit,
Il y a une certaine joie à savoir que vous avez écrit quelque chose de juste - de la manière idéale.
Et maintenant, je vais chercher du scotch et mettre de la musique électronique allemande ...
A la question générale de "méthodes pour éviter un débordement de pile dans un algorithme récursif" ...
Une autre approche consiste à inclure un compteur de récursivité. C’est davantage pour détecter des boucles infinies causées par des situations indépendantes de la volonté (et un mauvais codage).
Le compteur de récurrence prend la forme de
int foo(arg, counter) {
if(counter > RECURSION_MAX) { return -1; }
...
return foo(arg, counter + 1);
}
Chaque fois que vous passez un appel, vous incrémentez le compteur. Si le compteur devient trop gros, vous obtenez une erreur (ici, un retour de -1, mais dans d'autres langues, vous préférerez peut-être une exception). L'idée est d'empêcher que des choses pires se produisent (erreurs de mémoire insuffisante) lors d'une récursion beaucoup plus profonde que prévu et probablement une boucle infinie.
En théorie, vous ne devriez pas avoir besoin de ça. En pratique, j'ai vu du code mal écrit qui a heurté ce problème en raison d'une pléthore de petites erreurs et de mauvaises pratiques de codage (problèmes de concurrence multithread où quelque chose change quelque chose en dehors de la méthode qui fait passer un autre thread à une boucle infinie d'appels récursifs).
Utilisez le bon algorithme et résolvez le bon problème. Spécifiquement pour la conjecture de Collatz, il apparaît que vous essayez de la résoudre de la manière xkcd :
Vous commencez à un numéro et faites une traversée d’arbres. Cela conduit rapidement à un très grand espace de recherche. Une exécution rapide pour calculer le nombre d'itérations pour la réponse correcte aboutit à environ 500 étapes. Cela ne devrait pas poser de problème pour la récursivité avec un petit cadre de pile.
Bien que connaître la solution récursive ne soit pas une mauvaise chose, il faut également savoir que la solution itérative est souvent préférable . Un certain nombre de façons d’aborder la conversion d’un algorithme récursif en un algorithme itératif s’observe sur Stack Overflow at Way pour passer de la récursion à l’itération .