web-dev-qa-db-ja.com

.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);
}
100
ripper234

JITコンパイルは、コンパイルフェーズの実行にあまり時間をかけないこと(したがって、短命のアプリケーションをかなり遅くすること)と、標準の事前コンパイルを使用してアプリケーションの長期的な競争力を維持するのに十分な分析を行わないこととの間のトリッキーなバランスをとる行為です。

興味深いことに、 NGen コンパイル手順は、最適化をより積極的にすることを目的としていません。これは、JITとNGenのどちらがマシンコードを担当したかによって動作が異なるバグを単に持ちたくないためだと思われます。

[〜#〜] clr [〜#〜] 自体は末尾呼び出しの最適化をサポートしますが、言語固有のコンパイラーは関連する opcode およびJIT喜んでそれを尊重する必要があります。 F# 's fscは、関連するオペコードを生成します(単純な再帰の場合は、全体を直接whileループに変換できます)。 C#のcscはサポートしていません。

詳細については このブログ投稿 を参照してください(最近のJITの変更を考慮すると、おそらく古くなっています)。 CLRは4.0で変更されることに注意してください x86、x64、およびia64はそれを尊重します

79
ShuggyCoUk

これ Microsoft Connectフィードバック送信 は質問に答えるはずです。マイクロソフトからの公式の回答が含まれているので、それで行くことをお勧めします。

提案をありがとう。 C#コンパイラの開発では、多くの時点でテールコール命令を発行することを検討しました。ただし、これを回避するために私たちを押し進めたいくつかの微妙な問題があります:1)実際には、CLRで.tail命令を使用することには、些細なオーバーヘッドコストがあります(最終的にテールコールとしてのジャンプ命令ではありません)テールコールが大幅に最適化されている関数型言語ランタイム環境など、それほど厳密ではない多くの環境で)。 2)テールコールを発行することが合法である実際のC#メソッドはほとんどありません(他の言語では、テール再帰が多いコーディングパターンが推奨され、テールコールの最適化に大きく依存する多くのメソッドは、実際にグローバル書き換え(Continuation Passing変換など) )末尾再帰の量を増やすため)。 3)部分的には2)が原因で、C#メソッドが成功するはずの深い再帰によりオーバーフローするケースはほとんどありません。

とはいえ、これについては引き続き検討しており、将来のコンパイラのリリースでは、.tail命令を発行することが理にかなっているパターンを見つける可能性があります。

ところで、指摘されているように、末尾再帰はx64で最適化されていることは注目に値します。

72
Noldorin

C#は、F#の目的であるため、末尾呼び出し再帰に対して最適化されません!

C#コンパイラーが末尾呼び出しの最適化を実行できない条件の詳細については、この記事を参照してください: JIT CLR末尾呼び出し条件

C#とF#の相互運用性

。 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#はラムダ計算の原理に基づいて設計されていることです。ラムダ計算の原理に関する非常に優れた本については、この無料の本を参照してください: コンピュータープログラムの構造と解釈、Abelson、Sussman、およびSussmanによる

F#のテールコールに関する非常に優れた入門記事については、この記事を参照してください: F#のテールコールの詳細な紹介 。最後に、非末尾再帰と末尾呼び出し再帰(F#の場合)の違いを説明する記事があります。 Fシャープの末尾再帰と非末尾再帰

15
devinbost

私は最近、64ビット用のC#コンパイラが末尾再帰を最適化すると言われました。

C#もこれを実装しています。常に適用されるわけではない理由は、末尾再帰の適用に使用される規則が非常に厳しいためです。

8

C#(またはJava)の末尾再帰関数には、 トランポリン手法 を使用できます。ただし、より良い解決策は(スタックの使用率だけに関心がある場合) この小さなヘルパー メソッドを使用して、同じ再帰関数の一部をラップし、関数を読み取り可能な状態に保ちながら反復することです。

3
naiem