Ceci est une ancienne révision du document !
Récursion
La récursion est gourmande en pile. Sachant que la taille est défaut est de 1 Mo sous Windows et 8 Mo sous Linux, il est facile d'atteindre la saturation. Thread Stack Size Archive du 31/05/2018 le 21/06/2020
Tail Call Optimization
Tous les algorithmes peuvent s'écrire soit sous forme d'une boucle (nécessite des données mutables), soit sous forme d'une récursion (immuabilité des données possible).
L'objectif du TCO est de convertir automatiquement une récursion (qui augmente la taille de la pile) en un jmp (qui n'incrémente pas la taille de la pile).
Cette optimisation ne se fait qu'en release :
- clang utilise
callen-O0etjnepour-O1,-O2,-O3,-Os,-Oz,-Og,-O,-O4, - gcc utilise
callen-O0,-O1,-Ogetjnepour-O2,-03,-Os,-Oz, - msvc utilise
callen/O1,/Ob,/Od,/Oi,/Os,/Ot,/Oyetjmpen/O2,/Ox.
- Exemple compatible avec
TCO
int factorial_accumulate(int n, int accum) { if (n == 1) // Absence de récursion. return accum; else // Récursion. // Mais comme il s'agit de la dernière opération de la fonction, // le TCO va privilégier une instruction jmp à un call. return factorial_accumulate(n - 1, n * accum); } int factorial(int n) { return factorial_accumulate(n, 1); }
Mais attention, il faut que le return fasse uniquement appel à la fonction de récursion.
Tail Call Optimization in C++ Archive du 23/12/2018 le 16/06/2020
- Exemple non compatible avec LTO.
L'implémentation ci-dessous pourrait ne pas être optimisée par le LTO. GCC 10, clang 10 optimise à partir de -O2 mais Visual Studio 2019 n'optimise pas à /O2.
int factorial(int n) { if (n == 1) return 1; else // On fait une multiplication en plus de l'appel à la fonction factorial. return n * factorial(n - 1); }
