java 递归堆栈溢出_Common Lisp:为什么我的尾递归函数会导致堆栈溢出?

我在理解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)))

我正在尝试使用输入n = 1000000在CLISP中运行这些功能。这是结果

[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实现具有尾部调用优化。 但是,大多数这样做(由于Java虚拟机的限制,我认为ABCL不会这样做)。

实施文档应告诉您应选择哪些优化设置以拥有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)

正如danlei指出的那样,第一个函数有一个错字,它应该自己调用,而不是addup,这实际上就是您所描述的函数。如果我纠正错字,它也会溢出。不过,我还是困惑于do构造比递归构造更强大。在clisp.org上浏览和浏览规范时,我似乎找不到任何有关TCO for CLISP的信息。如果尾部递归没有比普通递归更强大,这会很奇怪吗?

您应该不会感到惊讶。尾部调用优化只是使递归定义在常量(堆栈)空间中运行,因此它可以与迭代定义一样快。没有任何魔术可以使之更快。

在《 Lisp的Barskis国度》中,我刚刚读到,确实CLISP仅在编译函数时优化尾调用。实际上,尾递归算法要快一点,所以这里没有什么神秘之处。

好吧,您的addup3根本不是递归的。

(defun addup3 (n)

(if (= n 0)

0

(+ n (addup (- n 1))))) ;

它调用任何addup。 在SBCL中尝试更正的版本:

CL-USER> (defun addup3 (n)

(if (= n 0)

0

(+ n (addup3 (- n 1)))))

ADDUP3

CL-USER> (addup3 100000)

Control stack guard page temporarily disabled: proceed with caution

;  ..

; Evaluation aborted on #.

如您所料。

Svante正确地处理了其他所有内容。

天哪,真傻。我真的很难检测到这种错别字。谢谢。我上面未包含的addup函数执行相同的操作,但是具有do构造。它本来不是要被调用的。

别担心,我们每个人都会时常犯这样的错误,很难发现它们。

使用GNU CommonLisp,GCL 2.6.12,addup2的编译将优化尾调用,这是我得到的:

>(compile 'addup2)

Compiling /tmp/gazonk_3012_0.lsp.

End of Pass 1.

;; Note: Tail-recursive call of F was replaced by iteration.

End of Pass 2.

OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3

Finished compiling /tmp/gazonk_3012_0.lsp.

Loading /tmp/gazonk_3012_0.o

start address -T 0x9556e8 Finished loading /tmp/gazonk_3012_0.o

#

NIL

NIL

>>(addup2 1000000)

500000500000

>(addup3 1000000)

Error: ERROR"Invocation history stack overflow."

Fast links are on: do (si::use-fast-links nil) for debugging

Signalled by IF.

ERROR"Invocation history stack overflow."

Broken at +.  Type :H for Help.

1  Return to top level.

>>(compile 'addup3)

Compiling /tmp/gazonk_3012_0.lsp.

End of Pass 1.

End of Pass 2.

OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3

Finished compiling /tmp/gazonk_3012_0.lsp.

Loading /tmp/gazonk_3012_0.o

start address -T 0x955a00 Finished loading /tmp/gazonk_3012_0.o

#

NIL

NIL

>>(addup3 1000000)

Error: ERROR"Value stack overflow."

希望能帮助到你。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值