Mu.
Seriamente ora, non importa. Non per esempi di queste dimensioni. Entrambi hanno la stessa complessità. Se il tuo codice non è abbastanza veloce per te, questo è probabilmente uno degli ultimi posti che guardi.
Ora, se vuoi davvero sapere quale è più veloce, misurali. Su SBCL, è possibile chiamare ciascuna funzione in un ciclo e misurare il tempo. Dato che hai due semplici funzioni, è sufficiente time
. Se il tuo programma fosse più complicato, un profiler sarebbe più utile. Suggerimento: se non hai bisogno di un profiler per le tue misure, probabilmente non devi preoccuparti delle prestazioni.
Sulla mia macchina (SBCL 64 bit), ho eseguito le funzioni e ha ottenuto questo:
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
Dopo aver messo le funzioni in un file con (declaim (optimize speed))
in alto, il tempo di ricorsione è sceso a 504 millisecondi e il il tempo di ciclo è sceso a 475 millisecondi.
E se vuoi davvero sapere cosa sta succedendo, prova dissasemble
sulle tue funzioni e guarda cosa c'è dentro.
Ancora una volta, questo mi sembra un non-problema. Personalmente, cerco di usare Common Lisp come un linguaggio di scripting per la prototipazione, quindi di profilare e ottimizzare le parti che sono lente. Passare da 500 a 475 ms non è niente. Ad esempio, in alcuni codici personali, ho ottenuto un paio di ordini di grandezza accelerando semplicemente l'aggiunta di un tipo di elemento a un array (rendendo così la memoria dell'array 64 volte più piccola nel mio caso). Certo, in teoria sarebbe stato più veloce riutilizzare quella matrice (dopo averla resa più piccola) e non allocarla più e più volte. Ma semplicemente aggiungere :element-type bit
è stato sufficiente per la mia situazione: più modifiche avrebbero richiesto più tempo per un piccolo vantaggio extra. Forse sono sciatto, ma "veloce" e "lento" non significano molto per me. Preferisco "abbastanza veloce" e "troppo lento". Entrambe le funzioni sono 'abbastanza veloci' nella maggior parte dei casi (o entrambe sono 'troppo lente' in alcuni casi) quindi non c'è una vera differenza tra loro.
Se la funzione è ricorsiva in coda, è fondamentalmente identica a un ciclo. La ricorsione della coda può essere ottimizzata in un ciclo semplice, rendendoli identici. Tuttavia, la tua funzione non è ricorsiva della coda. – Gabe
sostituire decf con 1-. –
@Gabe, Mentre la ricorsione della coda può essere ottimizzata per un ciclo, vale la pena notare che le implementazioni di Common Lisp non sono necessarie per ottimizzare le chiamate tail, anche se molte implementazioni lo fanno. –