读深深嵌套的树会导致堆栈溢出

时间:2015-05-07 23:21:38

标签: recursion lisp common-lisp stack-overflow s-expression

我正在尝试从文件中读取大量的sexp到内存中,对于较小的输入似乎工作得很好,但是对于更深层次的嵌套,sbcl会出现堆栈耗尽。似乎有一个硬递归限制(在1000个函数深处),sbcl根本无法超越(奇怪的是,即使它的堆栈大小增加)。示例(代码为here):make check-c有效,但make check-cpp耗尽了堆栈,如下所示:

INFO: Control stack guard page unprotected
Control stack guard page temporarily disabled: proceed with caution
Unhandled SB-KERNEL::CONTROL-STACK-EXHAUSTED in thread #<SB-THREAD:THREAD
                                                         "main thread" RUNNING
                                                          {10034E6DE3}>:
  Control stack exhausted (no more space for function call frames).
This is probably due to heavily nested or infinitely recursive function
calls, or a tail call that SBCL cannot or has not optimized away.

PROCEED WITH CAUTION.

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {10034E6DE3}>
0: ((LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX))
1: (SB-IMPL::CALL-WITH-SANE-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {100FC9006B}>)
2: (SB-IMPL::%WITH-STANDARD-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {100FC9003B}>)
...

为什么我使用递归呢?实际上,我不是,但不幸的是内置(read)使用递归,而这正是堆栈溢出发生的地方。另一个选项(我已经开始研究)是编写read的迭代版本,它依赖于我从单独的程序中提供的更有限的语法,以避免重新实现的复杂性read(我的(当前已损坏的)尝试位于上述存储库的lisp分支中。)

但是,我更喜欢更典型的解决方案。是否有内置read的替代方法可以通过避免递归来解析深层嵌套的结构?

编辑:对于sbcl本身而言,这似乎是一个不可逾越的问题,而不是输入数据。有关快速示例,请尝试运行:

(for i in $(seq 1 2000); do
echo -n "("
done; echo -n "2"; for i in $(seq 1 2000); do
echo -n ")"
done; echo) > file

然后在sbcl:

(with-open-file (file "file" :direction :input) (read file))

同样的失败发生。

编辑:在#sbcl上询问,显然控制堆栈大小确实仅适用于新线程,并且主线程的堆栈大小也受到许多其他因素的影响。所以我尝试将读取放在一个单独的线程中。仍然没有奏效。如果您有兴趣,可以结帐this repo并运行make check

1 个答案:

答案 0 :(得分:1)

我不知道你做了什么(因为你没有完全显示出来),但当我按照以下方式启动sbcl时,你的例子对我来说很好:

sbcl --control-stack-size 100

当然我推荐使用GNU CLISP和Embedded Common Lisp,因为它们也可以为你的例子提供A-OK。

我将为未来的读者添加对此答案的引用:https://stackoverflow.com/a/9002973/816536

我还要提到,在许多CL实现中,可能需要使用适当的优化选项编译代码,以便从尾部调用优化中受益。