为什么.NET / C#没有针对尾部进行优化

我发现了关于哪些语言优化尾递归的问题。 为什么C#不尽可能地优化尾递归?

对于具体情况,为什么不将该方法优化为循环(Visual Studio 2008 32位,如果重要的话)?:

private static void Foo(int i)
{
    if (i == 1000000)
        return;

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

    Foo(i+1);
}

JIT编译是一个棘手的平衡行为,不需要花太多时间进行编译阶段(从而大大减缓了短期应用程序),也没有进行足够的分析以保持应用程序的长期竞争力,而且具有标准的提前编译。

有趣的是,NGen编译步骤并不是针对在优化方面更具侵略性。 我怀疑这是因为他们根本不想在行为取决于JIT或NGen是否对机器代码负责的情况下出现错误。

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

请参阅此博客文章以获取一些详细信息(由于最近的JIT更改,现在很可能已过时)。 请注意,CLR更改为4.0,x86,x64和ia64会尊重它。


此Microsoft Connect反馈提交应回答您的问题。 它包含了来自微软的官方回应,所以我会建议你这样做。

感谢您的建议。 我们已经考虑在C#编译器开发中的许多要点上发出尾部调用指令。 但是,有一些细微的问题促使我们避免这种情况:1)在CLR中使用.tail指令实际上存在一笔不小的开销成本(它不仅仅是跳转指令,因为尾部调用最终成为在许多不太严格的环境中,例如功能语言运行时环境,其中尾部调用被大量优化)。 2)很少有真正的C#方法可以合法地发出尾部调用(其他语言鼓励编码模式有更多的尾部递归,而且很多很大程度上依赖尾部调用优化的实际上是全局重写(如Continuation Passing转换)增加尾递归量)。 3)部分原因在于2),由于深递归导致应用成功的C#方法堆栈溢出的情况相当少见。

总之,我们继续关注这一点,并且我们可能在未来的编译器发行版中找到一些模式,以便发出.tail指令。

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


C#没有针对尾调用递归进行优化,因为这是F#的用途!

有关阻止C#编译器执行尾部调用优化的条件的深入信息,请参阅此文章:JIT CLR尾部调用条件。

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

C#和F#互操作性非常好,而且因为.NET Common Language Runtime(CLR)是为了兼顾这种互操作性而设计的,所以每种语言的设计都是针对其意图和目的的特定优化。 有关示例显示从C#代码调用F#代码有多容易,请参阅从C#代码调用F#代码; 有关从F#代码调用C#函数的示例,请参阅从F#调用C#函数。

有关委托互操作性,请参阅此文章:委派F#,C#和Visual Basic之间的互操作性。

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

下面是一篇介绍一些差异的文章,并解释了C#和F#之间尾部递归的设计差异:在C#和F#中生成尾部调用操作码。

这里有一篇文章,其中包含C#,F#和C ++ CLI中的一些示例:C#,F#和C ++ CLI中的尾递归冒险

主要的理论差异在于C#被设计为循环,而F#被设计为基于Lambda演算的原则。 关于Lambda微积分原理的一本非常好的书,请参阅Abelson,Sussman和Sussman的免费书籍:计算机程序的结构和解释。

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

链接地址: http://www.djcxy.com/p/80609.html

上一篇: Why doesn't .NET/C# optimize for tail

下一篇: Debug vs. Release performance