Java中递归和非递归函数的效率比较

时间:2011-03-11 00:39:47

标签: c# java recursion performance

据我所知,由于函数调用的开销,递归函数通常效率低于等效的非递归函数。但是,我最近遇到了一本教科书,说Java(和C#)不一定是这样。

它没有说明原因,但我认为这可能是因为Java编译器以某种方式优化了递归函数。

有谁知道为什么会这样的细节?

6 个答案:

答案 0 :(得分:5)

教科书可能是指尾调优化;有关详细信息,请参阅@ Travis的答案。

但是,在Java的上下文中,教科书是不正确的。当前的Java编译器没有实现尾调用优化,显然是因为它会干扰Java安全实现,并且会出于各种目的改变对调用堆栈进行内省的应用程序的行为。

参考文献:

有一些提示,尾部调用优化可能会进入Java 8。

答案 1 :(得分:4)

这通常仅适用于尾递归(http://en.wikipedia.org/wiki/Tail_call)。

尾递归在语义上等同于递增的循环,因此可以优化为循环。以下是我与之相关的文章(强调我的):

  

尾巴通话很重要,因为   它们可以不加添加地实现   调用堆栈的新堆栈帧。   当前的大部分框架   不再需要程序,并且   它可以被替换为框架   尾调用,适当修改。   然后该程序可以跳转到   叫做子程序。生成这样的代码   而不是标准的呼叫序列   称为尾部呼叫消除,或尾部   通话优化。   在函数式编程语言中,   通常消除尾部呼叫   由语言标准保证,   这个保证允许使用   递归,特别是尾巴   递归,代替循环

答案 2 :(得分:4)

在某些情况下递归实现与迭代实现一样有效的一些原因:

  • 编译器可以足够聪明,可以针对某些功能优化函数调用,例如通过将尾递归函数转换为循环。我强烈怀疑Java的一些现代JIT编译器可以做到这一点。
  • 现代处理器进行分支预测和推测执行,这可能意味着函数调用的成本最低,或者至少不比迭代循环的成本高得多
  • 在每个递归级别需要少量本地存储的情况下,通过递归函数调用将其置于堆栈通常更高效,而不是以其他方式分配它(例如,通过堆内存中的队列)。

然而,我的一般建议是不要担心这一点 - 差异太小,不太可能对您的整体表现产生影响。

答案 3 :(得分:2)

Java的父亲之一盖伊斯蒂尔于1977年撰写了一篇论文

    Debunking the "Expensive Procedure Call" Myth
or, Procedure Call Implementations Considered Harmful
          or, LAMBDA: The Ultimate GOTO
  

摘要:   Folklore声明GOTO声明是   “便宜”,而程序调用“很贵”。这个   神话很大程度上是由于语言设计不佳造成的   的实施方式。

这很有趣,因为即使在今天,Java也没有尾调用优化:)

答案 4 :(得分:1)

据我所知,Java 进行任何类型的递归优化。知道这一点很重要 - 不是因为效率,而是因为过度深度的递归(几千人应该这样做)会导致堆栈溢出并导致程序崩溃。 (真的,考虑到这个网站的名称,我很惊讶没有人在我面前提起这件事。)

答案 5 :(得分:0)

我不这么认为,根据我在解决UVASPOJ等网站中的一些编程问题的经验,我必须删除递归以便在既定时间内解决问题以解决问题问题

你可以想到的一种方法是:在递归调用中,在递归发生的任何时候,jvm必须为被调用的函数分配资源,在非递归函数中,大部分内存已经被分配。