Mu.
Poważnie, teraz to nie ma znaczenia. Nie dla przykładów tej wielkości. Oba mają taką samą złożoność. Jeśli Twój kod nie jest wystarczająco szybki, prawdopodobnie jest to jedno z ostatnich miejsc, na które patrzysz.
Teraz, jeśli naprawdę chcesz wiedzieć, który jest szybszy, zmierz je. W SBCL możesz wywoływać każdą funkcję w pętli i mierzyć czas. Ponieważ masz dwie proste funkcje, wystarczy tylko time
. Jeśli Twój program byłby bardziej skomplikowany, bardziej przydatna byłaby wersja profiler. Wskazówka: jeśli nie potrzebujesz profilera do pomiarów, prawdopodobnie nie musisz martwić się wydajnością.
Na moim komputerze (SBCL 64 bit), wpadłem swoje funkcje i dostał to:
CL-USER> (time (loop repeat 1000 do (factorial_recursion 1000)))
Evaluation took:
0.540 seconds of real time
0.536034 seconds of total run time (0.496031 user, 0.040003 system)
[ Run times consist of 0.096 seconds GC time, and 0.441 seconds non-GC time. ]
99.26% CPU
1,006,632,438 processor cycles
511,315,904 bytes consed
NIL
CL-USER> (time (loop repeat 1000 do (factorial_loop 1000)))
Evaluation took:
0.485 seconds of real time
0.488030 seconds of total run time (0.488030 user, 0.000000 system)
[ Run times consist of 0.072 seconds GC time, and 0.417 seconds non-GC time. ]
100.62% CPU
902,043,247 processor cycles
511,322,400 bytes consed
NIL
Po oddaniu swoich funkcji w pliku z (declaim (optimize speed))
na górze, raz rekurencja spadła do 504 milisekund i tym czas pętli spadł do 475 milisekund.
A jeśli naprawdę chcesz wiedzieć, co się dzieje, spróbuj dissasemble
o swoich funkcjach i zobacz, co tam jest.
Ponownie, wygląda na to, że nie jestem dla mnie problemem. Osobiście staram się używać Common Lisp jak języka skryptowego do prototypowania, a następnie profilować i optymalizować części, które są wolne. Uzyskanie od 500ms do 475ms jest niczym. Na przykład w niektórych osobistych kodach uzyskałem kilka rzędów wielkości przyspieszenia, po prostu dodając typ elementu do tablicy (co czyni 64-krotnie mniej pamięci w moim przypadku). Oczywiście, teoretycznie byłoby szybciej użyć tej tablicy (po jej zmniejszeniu) i nie rozdzielać jej w kółko. Ale dodanie do tego tylko :element-type bit
było wystarczające dla mojej sytuacji - więcej zmian wymagałoby więcej czasu na bardzo niewiele dodatkowych korzyści. Może jestem niechlujny, ale "szybki" i "wolny" nie mają dla mnie większego znaczenia. Wolę "wystarczająco szybko" i "zbyt wolno". Obie funkcje są "wystarczająco szybkie" w większości przypadków (lub oba są "zbyt wolne" w niektórych przypadkach), więc nie ma między nimi żadnej rzeczywistej różnicy.
Jeśli twoja funkcja jest rekursywna, jest zasadniczo identyczna z pętlą. Rekurencję ogona można zoptymalizować w prostą pętlę, dzięki czemu są one identyczne. Jednak twoja funkcja nie jest rekurencyjna. – Gabe
zamień decf na 1-. –
@Gabe, Podczas gdy rekursja ogona może być zoptymalizowana do pętli, warto zauważyć, że implementacje Common Lisp nie są wymagane do optymalizacji wywołań typu tail, chociaż wiele implementacji robi. –