递归运行时实现Java与其他/功能语言?

时间:2010-09-28 10:15:37

标签: java programming-languages recursion functional-programming

我喜欢递归,但在Java中,你在某个时刻遇到了死胡同。例如。我有一个案例,其中~100K迭代的递归不起作用(StackOverflowError)。糟糕的是,我不得不为这个运行时堆栈限制原因切换到恼人的“命令性循环”。

我想知道其他(特别是功能性)语言在运行时是如何绕过堆栈溢出的?我想特别是函数式语言运行时更好地处理这个问题,因为递归是核心概念......

有人提供某些信息或外部资源吗?

5 个答案:

答案 0 :(得分:8)

大多数语言都对tail recursion进行了编译器优化。尾递归意味着递归调用应该是递归方法的最后一次调用。然后,编译器可以将其优化为循环,防止发生堆栈溢出错误。

我不确定所有javac实现是否都实现了尾递归。它不是规范要求的东西。但是,它是任何递归程序的重要优化技术,所以我认为主流实现确实提供尾递归。

您可以通过采用生成StackOverflowError的(简单)非尾递归程序并使其尾递归(例如计算factorial)来自行测试。

编辑:之前在Java中有question about tail recursion,如用户sje397的评论中所示。另请查看此问题的Stephen C's answer,其中提供了一些其他信息。

答案 1 :(得分:3)

这是@Ronald Wildenberg回答的后续内容:

  

我不确定所有javac实现是否都实现了尾递归。它不是规范要求的东西。

简短的回答是他们不支持它。

更长的答案是,由于JVM设计,尾递归优化在Java中是一个棘手的问题。阅读John Rose @ Oracle的this blog entry,他谈到了这个问题。博客条目的主旨是提出字节码扩展以支持“硬”尾调用。但最后一段暗示为什么实施“软”(即透明)尾部调用很难。尾调用优化会干扰JVM捕获堆栈跟踪的能力,这对Java安全体系结构具有“影响”。

Sun Bug Database Entry提供了有关此问题的更多详细信息。阅读评论。

似乎在当前JVM上实现尾递归的方法是在编译器前端实现它。显然Scala就是这样做的。

答案 2 :(得分:2)

“带有~100K迭代的递归”是应该避免的,而不仅仅是Java。它仅适用于尾部调用优化,但这并不总是可行的。因此,最好不要首先养成过度递归的习惯。

递归是人们在第一次学习它们时往往会过度使用的概念之一,因为它似乎太酷了,不能到处展示......

答案 3 :(得分:0)

您可以使用以下命令增加java堆栈大小:

java -Xss1024k MyProgram

(将java的堆栈大小增加到1024 KB。) 但通常使用深度递归并不是一个好主意。尝试制作迭代解决方案。

答案 4 :(得分:0)

正如其他人所提到的,支持正确的尾递归有帮助。但是它本身并不足以支持深度递归。尽管BLUB程序员可能会想到,深度递归很适合某些任务,例如处理深度递归的数据结构。

支持深度(非尾部)递归的策略通常归入支持一流延续的策略中。您可能会发现Implementation Strategies for First-Class Continuations(Clinger等人)很有趣。

我的头脑中有一些策略:

  • CPS转换您的程序或以其他方式堆分配延续帧(缺点:丢失堆栈的一些性能优势)
  • 在堆栈溢出时,将堆栈复制到单独的内存块并将堆栈指针重置为基址;在下溢时,将旧堆栈复制回来
  • 在堆栈溢出时,从堆中分配一个新的堆栈区域并在那里重置堆栈指针;在下溢,回到旧堆栈的末尾
  • 关于堆栈溢出,创建一个新线程继续运行计算,并等待线程的结果(缺点:创建一个线程可能很昂贵(但工作线程可以被缓存),并且跨线程移动计算可能会导致havok使用线程本地存储等)