sbcl在第二次调用函数时永远运行

时间:2010-02-25 02:09:12

标签: recursion lisp common-lisp slime sbcl

功能:

给定一个列表lst返回列表内容长度为k的所有排列,如果没有提供则默认为列表长度。

(defun permute (lst &optional (k (length lst)))
  (if (= k 1)
   (mapcar #'list lst)
   (loop for item in lst nconcing
     (mapcar (lambda (x) (cons item x)) 
             (permute (remove-if (lambda (x) (eq x item)) lst) 
                      (1- k))))))

问题: 我在连接到sbcl的emacs中使用SLIME,我还没有做太多的自定义。该功能在较小的输入上工作正常,例如lst ='(1 2 3 4 5 6 7 8)k = 3,这在实践中主要用于它。然而,当我连续两次使用大输入调用它时,第二个调用永远不会返回,并且sbcl甚至不会显示在顶部。这些是REPL的结果:

CL-USER> (time (nth (1- 1000000) (permute '(0 1 2 3 4 5 6 7 8 9))))
Evaluation took:
12.263 seconds of real time
12.166150 seconds of total run time (10.705372 user, 1.460778 system)
[ Run times consist of 9.331 seconds GC time, and 2.836 seconds non-GC time. ]
99.21% CPU
27,105,349,193 processor cycles
930,080,016 bytes consed

(2 7 8 3 9 1 5 4 6 0)
CL-USER> (time (nth (1- 1000000) (permute '(0 1 2 3 4 5 6 7 8 9))))

它永远不会从第二次通话中回来。我只能猜测,由于某种原因,我正在为垃圾收集器做一些可怕的事情,但我看不清楚是什么。有没有人有任何想法?

3 个答案:

答案 0 :(得分:4)

你的代码中有一件事是你使用EQ。 EQ比较身份。

EQ不是用于比较数字。两个数字的均衡可以是真或假。

如果要按身份,按值或字符进行比较,请使用EQL。不是EQ。

实际上

(remove-if (lambda (x) (eql x item)) list)

只是

(remove item list)

对于您的代码,EQ错误 COULD 意味着在递归调用中调用permute而实际上没有从列表中删除数字。

除此之外,我认为SBCL正忙于内存管理。我的Mac上的SBCL获得了大量内存(超过1 GB),并且忙于做某事。一段时间后,计算结果。

你的递归函数会产生大量的“垃圾”。 LispWorks说:1360950192字节

也许您可以提出更有效的实施方案?

更新:垃圾

Lisp提供了一些自动内存管理,但这并不能让程序员免于考虑空间效应。

Lisp使用堆栈和堆来分配内存。堆可以以某种方式为GC构建 - 例如在代,半空间和/或区域中。有精确的垃圾收集器和“保守”垃圾收集器(英特尔机器上的SBCL使用)。

当程序运行时,我们可以看到各种效果:

  1. 正常的递归过程在堆栈上分配空间。问题:堆栈大小通常是固定的(即使一些Lisps可以在错误处理程序中增加它)。

  2. 程序可能会分配大量内存并返回大量结果。 PERMUTE就是这样一个功能。它可以返回非常大的列表。

  3. 程序可以分配内存并暂时使用它,然后垃圾收集器可以回收它。即使程序没有使用大量的固定内存,创建和销毁的速度也会非常高。

  4. 但是还有更多问题。但是对于上面的每一个,Lisp程序员(以及使用垃圾收集的语言实现的每个其他程序员)都必须学习如何处理它。

    1. 用迭代替换递归。用尾递归替换递归。

    2. 仅返回所需结果的一部分,但不生成完整解决方案。如果你需要第n个排列,那么计算那个而不是所有的排列。使用按需计算的延迟数据结构。使用像SERIES这样的东西,允许使用流式但高效的计算。请参阅SICP,PAIP和其他高级Lisp书籍。

    3. 使用资源管理器重用内存。重用缓冲区而不是一直分配对象。使用高效的垃圾收集器,特别支持收集短暂(短暂的)物体。有时它也可能有助于破坏性地修改对象,而不是分配新对象。

    4. 以上涉及真实节目的空间问题。理想情况下,我们的编译器或运行时基础结构可以提供一些自动支持来处理这些问题。但实际上这并没有真正起作用。大多数Lisp系统提供低级功能来处理这个问题,而Lisp提供了可变对象 - 因为现实世界的Lisp程序的经验表明程序员确实希望使用它们来优化他们的程序。如果您有一个大型CAD应用程序来计算涡轮叶片的形式,那么关于非可变内存的理论/纯粹观点根本不适用 - 开发人员需要更快/更小的代码和更小的运行时间。

答案 1 :(得分:2)

大多数平台上的SBCL都使用分代垃圾收集器,这意味着分配的内存比一定数量的收集更多,因此很少考虑收集。你给定测试用例的算法会产生很多垃圾,它会触发GC很多次,以至于显然必须在整个函数运行时间内存活下来的实际结果是终身的,也就是说,移动到最后一代,很少收集或者根本没有。因此,对于32位系统的标准设置,第二次运行将耗尽堆(512 MB,可以通过运行时选项增加)。

通过使用(sb-ext:gc :full t)手动触发收集器,可以对终端数据进行垃圾回收。这显然取决于实现。

答案 2 :(得分:1)

从输出的外观来看,你正在看slime-repl,对吧?

尝试更改为“* inferior-lisp *”缓冲区,您可能会看到SBCL已下降到ldb(内置低级调试器)。最有可能的是,你已经设法打破了调用堆栈。