以下函数通过尾递归和平方来计算Fibonacci系列:
(defun fib1 (n &optional (a 1) (b 0) (p 0) (q 1))
(cond ((zerop n) b)
((evenp n)
(fib1 (/ n 2)
a
b
(+ (* p p) (* q q))
(+ (* q q) (* 2 p q))))
(t
(fib1 (1- n)
(+ (* b q) (* a (+ p q)))
(+ (* b p) (* a q))
p
q))))
基本上它将每个奇数输入减少到偶数,并将每个偶数输入减少一半。例如,
F(21)
= F(21 1 0 0 1)
= F(20 1 1 0 1)
= F(10 1 1 1 1)
= F(5 1 1 2 3)
= F(4 8 5 2 3)
= F(2 8 5 13 21)
= F(1 8 5 610 987)
= F(0 17711 10946 610 987)
= 10946
当我看到这个时,我认为结合偶数和奇数情况可能会更好(因为奇数减去一个=偶数),所以我写了
(defun fib2 (n &optional (a 1) (b 0) (p 0) (q 1))
(if (zerop n) b
(fib2 (ash n -1)
(if (evenp n) a (+ (* b q) (* a (+ p q))))
(if (evenp n) b (+ (* b p) (* a q)))
(+ (* p p) (* q q))
(+ (* q q) (* 2 p q)))))
并希望这会使它更快,因为现在上面的等式变为
F(21)
= F(21 1 0 0 1)
= F(10 1 1 1 1)
= F(5 1 1 2 3)
= F(2 8 5 13 21)
= F(1 8 5 610 987)
= F(0 17711 10946 1346269 2178309)
= 10946
然而,当我通过以下代码检查Fib(1000000)所需的时间时,它变得慢得多(在例如Clozure CL,CLisp和Lispworks中花费大约50%的时间)(忽略预测,我只是不希望我的屏幕上有数字。)
(time (progn (fib1 1000000)()))
(time (progn (fib2 1000000)()))
我只能看到fib2可能比fib1做的更多,所以为什么它会慢得多?
编辑:我想是的。做对了,我编辑了第二组公式。例如。在上面的F(21)的例子中,fib2实际上计算p和q中的F(31)和F(32),这是从未使用过的。所以在F(1000000)中,fib2计算F(1048575)和F(1048576)。懒惰的评价摇滚,这是一个非常好的观点。我猜在Common Lisp中只有一些像“和”和“或”这样的宏被懒惰地评估了?
以下修改的fib2(为n> 0定义)实际上运行得更快:
(defun fib2 (n &optional (a 1) (b 0) (p 0) (q 1))
(if (= n 1) (+ (* b p) (* a q))
(fib2 (ash n -1)
(if (evenp n) a (+ (* b q) (* a (+ p q))))
(if (evenp n) b (+ (* b p) (* a q)))
(+ (* p p) (* q q))
(+ (* q q) (* 2 p q)))))
答案 0 :(得分:3)
插入中间结果的打印。在计算结束时注意p
和q
。
您会注意到fib2
在最后一步为p
和q
计算了更大的值。这两个值可以解释所有性能差异。
答案 1 :(得分:0)
如果没有别的,fib2有更多的条件(计算参数时)。这可能会改变代码流的完成方式。条件意味着跳跃,意味着管道停滞。
查看生成的代码可能会很有启发性(尝试(disassemble #'fib1)
和(disassemble #'fib2)
,看看是否有任何明显的差异)。更改优化设置可能也是值得的,除非您要求对速度进行大量优化,否则通常会进行一些未完成的优化。