Tenemos dos funciones que calculan el factorial de un número dado. El primero, !
, usa un estilo de acumulador. El segundo, fact
, usa recursión natural.Rendimiento del estilo recursivo vs acumulador
(define (! n0)
(local (;; accumulator is the product of all natural numbers in [n0, n)
(define (!-a n accumulator)
(cond
[(zero? n) accumulator]
[else (!-a (sub1 n) (* n accumulator))])))
(!-a n0 1)))
y
(define (fact n)
(cond
[(= 0 n) 1]
[else (* n (fact (- n 1)))]))
En la parte inferior de la sección 31, HtDP indica que la versión recursiva de forma natural es a menudo tan rápido si no más rápido que la versión acumulador, pero no indica las razones por qué. Leí un poco sobre esto y parece que la respuesta es 'tail call optimization/elimination', pero el artículo de Wikipedia parece estar en desacuerdo con lo que dice HtDP, al menos con respecto al rendimiento. ¿Por qué esto es tan?
En el trabajo, el estilo recursivo es más rápido. En casa, el estilo del acumulador es más rápido. ¿No hay una heurística general para guiar una elección en cuanto a qué estilo se prefiere generalmente? Entiendo que el estilo de acumulador es más eficiente con la memoria, pero si restringimos la discusión al rendimiento solo, no está claro, al menos para mí, cuál es la mejor opción.
He pensado en esto un poco más, y tendría que ponerse de parte de un artículo de Wikipedia sobre la superioridad de la recursividad de estilo acumulador en el caso general . No solo reduce el uso del espacio de pila/montón, el acceso a la memoria siempre estará detrás del acceso de registro y solo se puede hacer más evidente ahora que la multinúcleo está aquí. Aún así, HtDP demuestra que se requieren pruebas reales en todos los casos.
Gracias por la información. Eso fue muy bien explicado. Lo ideal sería si alguien pudiera verificarlo en una versión de Racket de Windows de 64 bits. – Greenhorn