很简单,什么是尾部调用优化?更具体地说,任何人都可以显示一些可以应用的小代码片段,而不是在哪里,并解释为什么?
language-agnostic algorithm recursion tail-recursion tail-call-optimization
我很难将我的大脑包裹在PEP 380周围.
[更新]
现在我明白了我的困难的原因.我使用过发电机,但从未真正使用过协程(由PEP-342引入).尽管有一些相似之处,但生成器和协同程序基本上是两个不同的概念.理解协同程序(不仅仅是生成器)是理解新语法的关键.
恕我直言协程是最晦涩的Python功能,大多数书籍使它看起来毫无用处和无趣.
感谢您的回答,但特别感谢agf和他与David Beazley演讲相关的评论.大卫摇滚.
我知道递归有时比循环更清晰,而且我不会询问何时应该使用递归迭代,我知道有很多问题已经存在.
我要问的是,递归是否比循环更快?对我来说,似乎总是能够改进循环并让它比递归函数更快地执行,因为循环不会不断地设置新的堆栈帧.
我特别关注在递归是处理数据的正确方法的应用程序中递归是否更快,例如在一些排序函数,二叉树等中.
假设我们要计算一些斐波那契数,以 997 为模。
因为n=500在 C++ 中我们可以运行
#include <iostream>
#include <array>
std::array<int, 2> fib(unsigned n) {
if (!n)
return {1, 1};
auto x = fib(n - 1);
return {(x[0] + x[1]) % 997, (x[0] + 2 * x[1]) % 997};
}
int main() {
std::cout << fib(500)[0];
}
Run Code Online (Sandbox Code Playgroud)
在 Python 中
def fib(n):
if n==1:
return (1, 2)
x=fib(n-1)
return ((x[0]+x[1]) % 997, (x[0]+2*x[1]) % 997)
if __name__=='__main__':
print(fib(500)[0])
Run Code Online (Sandbox Code Playgroud)
两者都可以毫无问题地找到答案 996。我们采用模数来保持合理的输出大小,并使用对来避免指数分支。
对于n=5000,C++ 代码输出 783,但 Python 会抱怨
RecursionError: …Run Code Online (Sandbox Code Playgroud) 在Real World Haskell中,第4章.函数式编程
用foldr写foldl:
-- file: ch04/Fold.hs
myFoldl :: (a -> b -> a) -> a -> [b] -> a
myFoldl f z xs = foldr step id xs z
where step x g a = g (f a x)
Run Code Online (Sandbox Code Playgroud)
上面的代码让我很困惑,有人打电话给dps用一个有意义的名字重写它,使它更清晰:
myFoldl stepL zeroL xs = (foldr stepR id xs) zeroL
where stepR lastL accR accInitL = accR (stepL accInitL lastL)
Run Code Online (Sandbox Code Playgroud)
其他人,Jef G,通过提供一个例子并逐步展示基础机制,做得非常出色:
myFoldl (+) 0 [1, 2, 3]
= (foldR step id [1, 2, …Run Code Online (Sandbox Code Playgroud) TL; TR
当我问这个问题时,我假设a StackOverflowException是一种阻止应用程序无限运行的机制.这不是真的.
A StackOverflowException未被检测到.
当堆栈没有分配更多内存的容量时抛出它.
[原始问题:]
这是一个普遍的问题,每个编程语言可能有不同的答案.
我不确定C#以外的语言如何处理堆栈溢出.
我今天经历了例外,并一直在思考如何StackOverflowException检测到它.我相信不可能说fe是否深度为1000次调用,然后抛出异常.因为在某些情况下,正确的逻辑可能会那么深.
在我的程序中检测无限循环的逻辑是什么?
StackOverflowExceptionclass:
https://msdn.microsoft.com/de-de/library/system.stackoverflowexception%28v=vs.110%29.aspx类文档中
提到的交叉引用StackOverflowException:https:
//msdn.microsoft.com/de -de /库/ system.reflection.emit.opcodes.localloc(v = vs.110)的.aspx
我刚刚在stack-overflow这个问题上添加了标记,并且描述说当调用堆栈消耗太多内存时它会被抛出.这是否意味着调用堆栈是我的程序的当前执行位置的某种路径,如果它不能存储更多的路径信息,那么抛出异常?
如何判断gcc(更具体地说,g ++)是否在特定函数中优化尾递归?(因为它出现了几次:我不想测试gcc是否可以优化尾递归.我想知道它是否优化了我的尾递归函数.)
如果您的答案是"查看生成的汇编程序",我想知道我正在寻找什么,以及我是否可以编写一个简单的程序来检查汇编程序以查看是否存在优化.
PS.我知道这似乎是问题的一部分,如果有的话,C++编译器会进行尾递归优化吗?从5个月前.但是,我不认为这个问题的这一部分得到了令人满意的答复.(答案是"检查编译器是否进行了优化(我知道)的最简单方法是执行调用,否则会导致堆栈溢出 - 或者查看汇编输出.")
出于好奇,我试图使用C#生成尾调用操作码.Fibinacci是一个简单的,所以我的c#示例如下所示:
private static void Main(string[] args)
{
Console.WriteLine(Fib(int.MaxValue, 0));
}
public static int Fib(int i, int acc)
{
if (i == 0)
{
return acc;
}
return Fib(i - 1, acc + i);
}
Run Code Online (Sandbox Code Playgroud)
如果我在发布中构建并在没有调试的情况下运行它,我就不会出现堆栈溢出.在没有优化的情况下调试或运行它,我确实得到了堆栈溢出,这意味着尾部调用在发布时具有优化功能(这是我的预期).
这个MSIL看起来像这样:
.method public hidebysig static int32 Fib(int32 i, int32 acc) cil managed
{
// Method Start RVA 0x205e
// Code Size 17 (0x11)
.maxstack 8
L_0000: ldarg.0
L_0001: brtrue.s L_0005
L_0003: ldarg.1
L_0004: ret
L_0005: ldarg.0
L_0006: ldc.i4.1
L_0007: sub
L_0008: ldarg.1
L_0009: ldarg.0
L_000a: …Run Code Online (Sandbox Code Playgroud) 我是F#的新手,正在阅读有关尾递归函数的内容,希望有人可以给我两个不同的函数foo实现 - 一个是尾递归的,另一个不是我可以更好地理解这个原理.
阅读了Rauschmayer博士对es6中递归尾调用优化的描述之后,我一直试图重新创建他详细说明的递归因子函数的"零堆栈"执行.
使用Chrome调试器在堆栈帧之间切换,我发现尾部优化没有发生,并且正在为每次递归创建堆栈帧.
我也尝试通过在没有调试器的情况下调用函数来测试优化,而是传递100000给阶乘函数.这会引发"最大堆栈"错误,这意味着它实际上并未进行优化.
这是我的代码:
const factorial = (n, acc = 1) => n <= 1 ? acc : factorial(n - 1, n * acc)
console.log( factorial(100000) )
Run Code Online (Sandbox Code Playgroud)
结果:
Uncaught RangeError: Maximum call stack size exceeded
Run Code Online (Sandbox Code Playgroud) javascript stack-overflow optimization recursion ecmascript-6
recursion ×6
c# ×2
f# ×2
python ×2
algorithm ×1
c++ ×1
cil ×1
ecmascript-6 ×1
fold ×1
g++ ×1
gcc ×1
haskell ×1
iteration ×1
javascript ×1
loops ×1
optimization ×1
performance ×1
yield ×1