2

我正在尝试将大量 sexp 从文件读取到内存中,对于较小的输入,它似乎工作得很好,但在更深的嵌套输入上,sbcl 会因堆栈耗尽而崩溃。似乎有一个硬递归限制(深度为 1000 个函数), sbcl 根本无法超过(奇怪的是,即使它的堆栈大小增加了)。示例(代码在此处):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它依赖于我从一个单独的程序中输入的更有限的语法,以避免重新实现读取的复杂性(我的(目前损坏)尝试在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,显然控制堆栈大小确实仅适用于新线程,并且主线程的堆栈大小也受到许多其他因素的影响。所以我尝试将读取放在单独的线程中。仍然没有工作。如果您有兴趣,请查看此 repo并运行。make check

4

1 回答 1

1

我不知道你做了什么(因为你没有准确地展示它),但是当我sbcl按如下方式开始时,你的例子对我来说很好:

sbcl --control-stack-size 100

当然,我推荐 GNU CLISP 和 Embedded Common Lisp,因为它们也适用于您的示例。

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

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

于 2015-05-14T18:38:33.657 回答