Wydaje mi się, że optymalizacja rekurencji ogonowej zarówno w C, jak i C ++ działałaby doskonale, ale podczas debugowania nigdy nie widzę stosu ramek, który wskazuje na tę optymalizację. To trochę dobrze, ponieważ stos mówi mi, jak głęboka jest rekursja. Jednak optymalizacja też byłaby miła.
Czy jakieś kompilatory C ++ wykonują tę optymalizację? Czemu? Dlaczego nie?
Jak mam powiedzieć kompilatorowi, żeby to zrobił?
- W przypadku MSVC:
/O2
lub/Ox
- W przypadku GCC:
-O2
lub-O3
Co powiesz na sprawdzenie, czy kompilator zrobił to w określonym przypadku?
- W przypadku MSVC włącz dane wyjściowe PDB, aby umożliwić śledzenie kodu, a następnie sprawdź kod
- Dla GCC ..?
Nadal brałbym sugestie, jak określić, czy dana funkcja jest zoptymalizowana w ten sposób przez kompilator (chociaż uważam za uspokajające, że Konrad mówi mi, żebym ją założył)
Zawsze można sprawdzić, czy kompilator w ogóle to robi, wykonując nieskończoną rekursję i sprawdzając, czy powoduje to nieskończoną pętlę lub przepełnienie stosu (zrobiłem to z GCC i okazało się, że -O2
jest to wystarczające), ale chcę być w stanie sprawdzić pewną funkcję, o której wiem, że i tak się zakończy. Chciałbym mieć łatwy sposób na sprawdzenie tego :)
Po kilku testach odkryłem, że destruktory rujnują możliwość dokonania tej optymalizacji. Czasami warto zmienić zakres niektórych zmiennych i tymczasowych, aby upewnić się, że wykraczają poza zakres, zanim rozpocznie się instrukcja return.
Jeśli jakikolwiek destruktor musi być uruchomiony po wywołaniu końcowym, nie można przeprowadzić optymalizacji wywołania ogonowego.
źródło
gcc
ma węższą opcję-foptimize-sibling-calls
„optymalizacji rodzeństwa i ogonienia wywołań rekurencyjnych”. Opcja ta (wedługgcc(1)
stron podręcznika dla wersji 4.4, 4.7 i 4.8 kierowania różnych platform) jest włączona na poziomach-O2
,-O3
,-Os
.gcc 4.3.2 całkowicie wbudowuje tę funkcję (kiepska / trywialna
atoi()
implementacja) domain()
. Poziom optymalizacji to-O1
. Zauważam, że jeśli się nim bawię (nawet zmieniając go zstatic
naextern
, rekurencja ogona znika dość szybko, więc nie polegałbym na tym w kwestii poprawności programu.źródło
extern
metoda może być wtedy wbudowana .-O1
nie ma inline i bez optymalizacji ogon rekursji . Trzeba użyć-O2
do tego (no, w 4.2.x, która jest raczej starożytny teraz, to nadal nie będzie inline). BTW Warto też dodać, że gcc może optymalizować rekurencję, nawet jeśli nie jest to ściśle ogonowa (jak silnia bez akumulatora).Poza oczywistością (kompilatory nie wykonują tego rodzaju optymalizacji, chyba że o to poprosisz), istnieje złożoność optymalizacji wywołań ogonowych w C ++: destruktory.
Biorąc pod uwagę coś takiego:
Kompilator nie może (generalnie) tego zoptymalizować, ponieważ musi wywołać destruktor
cls
po powrocie wywołania rekurencyjnego.Czasami kompilator widzi, że destruktor nie ma zewnętrznych widocznych skutków ubocznych (więc można to zrobić wcześniej), ale często nie.
Szczególnie powszechną formą tego jest sytuacja, w której
Funky
faktycznie występuje astd::vector
lub podobny.źródło
Większość kompilatorów nie przeprowadza żadnej optymalizacji w kompilacji do debugowania.
Jeśli używasz VC, wypróbuj kompilację wydania z włączonymi informacjami PDB - pozwoli ci to prześledzić zoptymalizowaną aplikację i miejmy nadzieję, że zobaczysz, czego chcesz. Należy jednak pamiętać, że debugowanie i śledzenie zoptymalizowanej kompilacji przeskakuje z każdego miejsca i często nie można bezpośrednio sprawdzić zmiennych, ponieważ trafiają one tylko do rejestrów lub są całkowicie zoptymalizowane. To „ciekawe” doświadczenie ...
źródło
Jak wspomina Greg, kompilatory nie zrobią tego w trybie debugowania. W porządku, gdy kompilacje debugowania są wolniejsze niż kompilacje prod, ale nie powinny częściej ulegać awarii: a jeśli polegasz na optymalizacji wywołań końcowych, mogą zrobić dokładnie to. Z tego powodu często najlepiej jest przepisać wywołanie tail jako normalną pętlę. :-(
źródło