我在理解 Common Lisp 函数的性能方面遇到了问题(我还是个新手)。我有这个函数的两个版本,它只是计算给定的所有整数的总和n
.
非尾递归版本:
(defun addup3 (n)
(if (= n 0)
0
(+ n (addup (- n 1)))))
尾递归版本:
(defun addup2 (n)
(labels ((f (acc k)
(if (= k 0)
acc
(f (+ acc k) (- k 1)))))
(f 0 n)))
我正在尝试使用输入在 LISP 中运行这些函数n = 1000000
。这是结果
[2]> (addup3 1000000)
500000500000
[3]> (addup2 1000000)
*** - Program stack overflow. RESET
我可以在 SBCL 中成功运行两者,但非尾递归更快(仅快一点,但这对我来说似乎很奇怪)。我在 Stackoverflow 上搜索了问题的答案,但找不到类似的东西。尽管尾递归函数设计为不将所有递归函数调用放入堆栈,但为什么会出现堆栈溢出?我是否必须告诉解释器/编译器优化尾部调用? (我读过类似的东西(proclaim '(optimize (debug 1))
设置调试级别并以跟踪能力为代价进行优化,但我不知道这是做什么的)。
也许答案是显而易见的,代码是废话,但我就是无法弄清楚。
感谢帮助。
编辑:danlei 指出了拼写错误,应该是调用addup3
在第一个函数中,所以它是递归的。如果更正,两个版本都会溢出,但他的版本不会溢出
(defun addup (n)
"Adds up the first N integers"
(do ((i 0 (+ i 1))
(sum 0 (+ sum i)))
((> i n) sum)))
虽然这可能是一种更典型的方法,但我觉得奇怪的是,尾递归并不总是优化的,考虑到我的导师喜欢告诉我它效率更高。
Common Lisp 实现不需要进行尾调用优化。然而,大多数都这样做(我认为 ABCL 不这样做,因为 Java 虚拟机的限制)。
实施文档应告诉您应选择哪些优化设置来实现 TCO(如果可用)。
对于 Common Lisp 代码来说,使用其中一种循环结构更为惯用:
(loop :for i :upto n
:sum i)
(let ((sum 0))
(dotimes (i n)
(incf sum (1+ i))))
(do ((i 0 (1+ i))
(sum 0 (+ sum i)))
((> i n) sum))
当然,在这种情况下,最好使用“小 Gauß”:
(/ (* n (1+ n)) 2)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)