【问题标题】:Does MATLAB perform tail call optimization?MATLAB 是否执行尾调用优化?
【发布时间】:2011-07-16 15:40:28
【问题描述】:

我最近学习了 Haskell,并且尽可能将纯函数式风格带到我的其他代码中。这方面的一个重要方面是将所有变量视为不可变的,即常量。为了做到这一点,许多将使用命令式循环实现的计算必须使用递归来执行,由于为每个函数调用分配新的堆栈帧,这通常会导致内存损失。然而,在尾调用的特殊情况下(被调用函数的返回值立即返回给被调用者的调用者),这种惩罚可以被称为尾调用优化的过程绕过(在一种方法中,这可以通过基本上在正确设置堆栈后用 jmp 替换调用)。 MATLAB 是否默认执行 TCO,还是有办法告诉它执行?

【问题讨论】:

  • 仅仅为了它而避免迭代并不是一个好主意。使用更适合给定问题的任何东西(并且可行 - 当然迭代在纯函数式语言中是不实用的)。
  • 通过适当的尾调用优化,“避免迭代”成为您如何看待问题的问题,而不是您的解决方案如何执行。如果 MATLAB 不提供 TCO,那么显然我会在需要时使用迭代,但如果提供了,我将能够在我的所有代码中使用一致的范例。
  • 我不了解任何 MATLAB,我说的是一般情况。如果您正在编写 Python 代码,并且 Python 具有 TCO,那么您仍然不应该在循环上使用递归,因为它不是惯用的,语言和 stdlib 专注于充分利用迭代器等。关于“一致范式”:每个范式有其弱点,因此请使用最能解决给定问题的任何方法(当然,“最佳”的定义还包括它与其他问题的协同程度)。

标签: matlab functional-programming tail-recursion tail-call-optimization


【解决方案1】:

如果我定义一个简单的尾递归函数:

function tailtest(n)
  if n==0; feature memstats; return; end
  tailtest(n-1);
end

并调用它,以便它会非常深入地递归:

set(0,'RecursionLimit',10000);
tailtest(1000);

那么看起来堆栈帧并没有占用大量内存。但是,如果我让它递归得更深:

set(0,'RecursionLimit',10000);
tailtest(5000);

然后(今天在我的机器上)MATLAB 简单地崩溃了:进程毫不客气地死掉了。

我不认为这与 MATLAB 做任何 TCO 是一致的;函数尾部调用自身的情况,只在一个地方,除了一个参数之外没有局部变量,这与任何人所希望的一样简单。

所以:不,MATLAB 似乎根本不做 TCO,至少在默认情况下是这样。我(到目前为止)还没有寻找可能启用它的选项。如果有的话我会很惊讶。

在我们不爆栈的情况下,递归的成本是多少?请参阅我对 Bill Cheatham 的回答的评论:看起来时间开销并非微不足道,但并非疯狂。

...除了比尔·奇塔姆(Bill Cheatham)在我发表评论后删除了他的答案。好的。因此,我采用了斐波那契函数的简单迭代实现和简单的尾递归函数,在两者中进行了基本相同的计算,并在fib(60) 上对它们进行了计时。递归实现的运行时间大约是迭代实现的 2.5 倍。当然,对于每次迭代比一次加法和一次减法做更多工作的函数来说,相对开销会更小。

(我也同意 delnan 的观点:在 Haskell 中感觉很自然的那种高度递归的代码在 MATLAB 中通常可能是单调的。)

【讨论】:

  • TCO 在 Matlab 中可能很难,因为使用 onCleanup() 功能从堆栈帧的工作区清除局部变量可能会产生副作用。 Matlab 不是 GCed Java 风格;它是确定性的,使用引用计数或类似的。支持 RAII。为了确定堆栈帧省略是否安全,它必须深入搜索未在尾调用中传递的所有局部变量,以查看它们是否包含任何 onCleanups。昂贵的测试。此外,在调用 assignin(...,'caller') 或 evalin(...,'caller') 的情况下,可能需要保留至少一个父堆栈帧。同意;单调的。
猜你喜欢
  • 2010-10-23
  • 2012-04-18
  • 2010-11-16
  • 1970-01-01
  • 2020-10-21
  • 1970-01-01
  • 2017-07-23
  • 2018-08-30
  • 2015-07-20
相关资源
最近更新 更多