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

Common lisp Common Lisp:为什么我的尾部递归函数会导致堆栈溢出?,common-lisp,tail-recursion,sbcl,clisp,Common Lisp,Tail Recursion,Sbcl,Clisp,我在理解常见Lisp函数的性能方面有问题(我还是个新手)。我有两个版本的函数,它只计算给定n的所有整数之和 非尾部递归版本: 尾部递归版本: 我正在尝试使用输入n=1000000在CLISP中运行这些函数。结果如下 [2]> (addup3 1000000) 500000500000 [3]> (addup2 1000000) *** - Program stack overflow. RESET 我可以在SBCL中成功地运行这两个函数,但非尾部递归函数更快(只快了一点,但对我来

我在理解常见Lisp函数的性能方面有问题(我还是个新手)。我有两个版本的函数,它只计算给定
n
的所有整数之和

非尾部递归版本:

尾部递归版本:

我正在尝试使用输入
n=1000000
在CLISP中运行这些函数。结果如下

[2]> (addup3 1000000)
500000500000
[3]> (addup2 1000000)

*** - Program stack overflow. RESET
我可以在SBCL中成功地运行这两个函数,但非尾部递归函数更快(只快了一点,但对我来说似乎很奇怪)。我已经搜索了很多问题的答案,但找不到类似的答案。尽管尾部递归函数的设计目的不是将所有递归函数调用放在堆栈上,但为什么会出现堆栈溢出?我是否必须告诉解释器/编译器优化尾部调用?(我读了一些类似于
(宣告“(优化(调试1))
的东西来设置调试级别并以跟踪能力为代价进行优化,但我不知道这有什么作用)。 也许答案是显而易见的,代码是胡说八道,但我就是搞不懂。 谢谢你的帮助

编辑:danlei指出了错误,它应该是对第一个函数中的
addup3
的调用,因此它是递归的。如果更正,两个版本都会溢出,但不是他的版本

(defun addup (n) 
         "Adds up the first N integers"
         (do ((i 0 (+ i 1)) 
              (sum 0 (+ sum i)))
             ((> i n) sum)))

虽然这可能是一种更为典型的方法,但考虑到我的导师喜欢告诉我,尾部递归并不总是得到优化,我觉得很奇怪。

普通的Lisp实现不需要尾部调用优化。然而,大多数情况下是这样的(由于Java虚拟机的局限性,我认为ABCL不会这样做)

实现的文档应该告诉您应该选择哪些优化设置来实现TCO(如果可用)

普通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))
当然,在这种情况下,最好使用“小高卢”:


好吧,你的
addup3
根本不是递归的


正如您所料。

使用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
#<compiled-function ADDUP2>
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
#<compiled-function ADDUP3>
NIL
NIL
>>(addup3 1000000)                                                                                                                                            

Error: ERROR "Value stack overflow."
>(编译“addup2”)
编译/tmp/gazonk_3012_0.lsp。
第1关结束。
注:F的尾部递归调用被迭代所取代。
第二关结束。
优化级别:安全=0(无运行时错误检查),空间=0,速度=3
已完成编译/tmp/gazonk_3012_0.lsp。
装载/tmp/gazonk_3012_0.o
起始地址-T 0x9556e8已完成加载/tmp/gazonk_3012_0.o
#
无
无
>>(合计200万)
500000500000
>(合计300万)
错误:错误“调用历史堆栈溢出”
快速链接已打开:do(si::use Fast links nil)用于调试
由IF发出信号。
错误“调用历史堆栈溢出”
在+处断开。键入:H以获取帮助。
1返回顶层。
>>(编译“addup3”)
编译/tmp/gazonk_3012_0.lsp。
第1关结束。
第二关结束。
优化级别:安全=0(无运行时错误检查),空间=0,速度=3
已完成编译/tmp/gazonk_3012_0.lsp。
装载/tmp/gazonk_3012_0.o
起始地址-T 0x955a00已完成加载/tmp/gazonk_3012_0.o
#
无
无
>>(合计300万)
错误:错误“值堆栈溢出”

希望能有帮助。

哦,天哪,真是太傻了。我真的不擅长检测这种打字错误。谢谢。
addup
函数(我在上面没有包含)也有同样的功能,但它有一个
do
构造。虽然它不是用来调用的。别担心,我们都会时不时地犯这样的错误,而且很难发现。正如但是danlei说,第一个函数有一个输入错误,它应该调用自己,而不是
addup
,这本质上就是您描述的函数。如果我纠正了输入错误,它也会溢出。不过,我还是很困惑
do
构造比递归构造更有能力。我似乎找不到任何关于使用googli时CLISP的TCO的信息如果尾部递归没有比普通递归更强大,那不是很奇怪吗?你不应该感到惊讶。尾部调用优化只是让递归定义在常量(堆栈)中运行空间,因此它可以像迭代定义一样快。没有什么魔法可以让它比这更快。在Barski的Lisp之地,我刚刚读到,当编译函数时,CLISP确实只优化尾部调用。事实上,尾部递归调用要快一点,所以这里没有什么神秘之处。
(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))
(/ (* n (1+ n)) 2)
(defun addup3 (n) 
  (if (= n 0)
    0   
    (+ n (addup (- n 1))))) ; <--
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 #<SB-SYS:MEMORY-FAULT-ERROR {C2F19B1}>.
>(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
#<compiled-function ADDUP2>
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
#<compiled-function ADDUP3>
NIL
NIL
>>(addup3 1000000)                                                                                                                                            

Error: ERROR "Value stack overflow."