为什么';NET/C#优化尾部调用递归?

为什么';NET/C#优化尾部调用递归?,c#,.net,optimization,tail-recursion,C#,.net,Optimization,Tail Recursion,我发现哪些语言优化了尾部递归。为什么C#不尽可能优化尾部递归 对于一个具体的例子,为什么这个方法没有优化成一个循环(32位,如果这很重要的话) 这应该能回答你的问题。它包含了微软的官方回复,所以我建议你这样做 谢谢你的建议。我们已经 考虑发出尾声 中多个点的说明 C#编译器的开发。 然而,也有一些微妙的问题 这促使我们避免这样做 far:1)实际上有一个 使用 .CLR中的tail指令(它是 不仅仅是一个跳转指令作为尾巴 电话最终变得越来越少 严格的环境,如功能 语言运行时环境,其中 尾部调用进

我发现哪些语言优化了尾部递归。为什么C#不尽可能优化尾部递归

对于一个具体的例子,为什么这个方法没有优化成一个循环(32位,如果这很重要的话)

这应该能回答你的问题。它包含了微软的官方回复,所以我建议你这样做

谢谢你的建议。我们已经 考虑发出尾声 中多个点的说明 C#编译器的开发。 然而,也有一些微妙的问题 这促使我们避免这样做 far:1)实际上有一个 使用 .CLR中的tail指令(它是 不仅仅是一个跳转指令作为尾巴 电话最终变得越来越少 严格的环境,如功能 语言运行时环境,其中 尾部调用进行了大量优化)。2) 很少有真正的C#方法能够 发出尾声是合法的吗 (其他语言鼓励编码 有更多尾巴的图案 递归,以及许多严重依赖 尾部调用优化实际上是这样做的 全局重写(例如 连续传递变换) 增加尾巴的数量 递归)。3) 部分原因是2), C#方法堆栈溢出的情况 由于深度递归,应该 成功的例子相当罕见

综上所述,我们继续关注 这一点,我们可能会在未来的版本 找到一些模式 在有意义的地方发射。尾巴 指示


顺便说一句,正如已经指出的,值得注意的是,在x64上对尾部递归进行了优化。

最近有人告诉我,64位C#编译器确实对尾部递归进行了优化


C#也实现了这一点。之所以不总是应用它,是因为用于应用尾部递归的规则非常严格。

JIT编译是一种在不花费太多时间进行编译阶段(从而大大降低了短期应用程序的速度)之间的微妙平衡与未进行足够的分析以保持应用程序的长期竞争力相比,采用标准的提前编译

有趣的是,编译步骤的目标并不是要在优化时更加积极。我怀疑这是因为他们根本不希望出现行为取决于JIT或NGen是否负责机器代码的bug

它本身确实支持尾部调用优化,但是特定于语言的编译器必须知道如何生成相关的调用,并且JIT必须愿意尊重它。 fsc将生成相关的操作码(尽管对于简单的递归,它可能只是将整个过程直接转换为
循环)。C#的csc没有

有关详细信息,请参阅(鉴于最近的JIT更改,现在很可能已经过时)。请注意,CLR在4.0中有所更改。

您可以在C#(或Java)中使用尾部递归函数。然而,更好的解决方案(如果您只关心堆栈利用率)是使用方法包装同一递归函数的部分,并使其迭代,同时保持函数可读性。

C#不优化尾部调用递归,因为这就是F#的用途

有关阻止C#编译器执行尾部调用优化的条件的详细信息,请参阅本文:

C#和F#之间的互操作性

C#和F#的互操作性非常好,而且由于.NET公共语言运行库(CLR)的设计考虑到了这种互操作性,因此每种语言都设计了针对其目的和用途的优化。有关显示从C代码调用F代码有多么容易的示例,请参阅;有关从F代码调用C函数的示例,请参阅

有关委托互操作性,请参阅本文:

C#和F#之间的理论和实践差异

这里有一篇文章介绍了一些差异,并解释了C#和F#之间尾部调用递归的设计差异:

下面是一篇文章,其中包含C#、F#和C++\CLI中的一些示例:

主要的理论区别在于C#是用循环设计的,而F#是根据Lambda微积分原理设计的。有关Lambda微积分原理的一本好书,请参阅以下免费书籍:


有关F#中尾部调用的非常好的介绍性文章,请参阅本文:。最后,这里有一篇文章介绍了非尾部递归和尾部调用递归(在F#)之间的区别:。

正如其他答案所提到的,CLR确实支持尾部调用优化,而且从历史上看,它似乎正在逐步改进。但是在C#中支持它在git存储库中有一个关于C#编程语言设计的公开的
建议
问题

你可以在那里找到一些有用的细节和信息。例如提到@jaykrell

让我把我知道的告诉你

有时tailcall是一种性能双赢。它可以节省CPU。jmp是 比call/ret更便宜,它可以节省堆栈。触摸较少的堆栈有助于 更好的地方

有时tailcall是一种性能损失,stack win。 CLR有一个复杂的机制,可以将更多参数传递给 被呼叫者比呼叫者收到的要多。我的意思是更具体地说 参数的空间。这太慢了。但它保存堆栈。会的 只能用尾巴做这个。前缀

如果调用方参数为 堆栈大于被调用方参数,这通常是一个相当容易的双赢 使改变可能存在参数位置变化等因素 从托管到整数/浮点,并生成精确的堆栈映射和 这样

现在,还有另一个角度,那就是要求 消除尾呼叫,以便能够处理 具有固定/小堆栈的任意大数据。这不是关于 性能,但关于r的能力
private static void Foo(int i)
{
    if (i == 1000000)
        return;

    if (i % 100 == 0)
        Console.WriteLine(i);

    Foo(i+1);
}