他の回答が述べたように、CLRは末尾呼び出しの最適化をサポートしており、歴史的には進歩的な改善があったようです。ただしProposal
、C#でのサポートには、C#プログラミング言語のサポートテール再帰#2544の設計に関するgitリポジトリで未解決の問題があります。
あなたはそこにいくつかの有用な詳細と情報を見つけることができます。たとえば、@ jaykrellについて
私が知っていることをあげましょう。
時々、テールコールはパフォーマンスに有利です。CPUを節約できます。jmpはcall / retよりも安価ですスタックを節約できます。少ないスタックに触れると、局所性が向上します。
時々、テールコールはパフォーマンスの低下、スタックの勝利です。CLRには、呼び出し元が受け取ったよりも多くのパラメーターを呼び出し先に渡す複雑なメカニズムがあります。具体的には、パラメーター用のより多くのスタック領域を意味します。これは遅いです。しかし、それはスタックを節約します。これは尾でのみ行われます。接頭辞。
呼び出し元のパラメーターが呼び出し先のパラメーターよりもスタックが大きい場合、通常は非常に簡単なwin-win変換です。パラメータの位置がマネージドから整数/浮動小数点に変更されたり、正確なStackMapが生成されたりするなどの要因があるかもしれません。
さて、固定/小さいスタックで任意の大きなデータを処理できるようにするために、テールコールの除去を要求するアルゴリズムの別の角度があります。これはパフォーマンスの問題ではなく、まったく実行できる能力の問題です。
また、(追加情報として)言及させてください。System.Linq.Expressions
名前空間の式クラスを使用してコンパイル済みラムダを生成するとき、コメントで説明されているように、「tailCall」という名前の引数があります。
作成された式をコンパイルするときに末尾呼び出しの最適化が適用されるかどうかを示すブール値。
まだ試していないので、質問に関連してどのように役立つかわかりませんが、おそらく誰かが試してみて、いくつかのシナリオで役立つかもしれません。
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
(すなわち、階乗アルゴリズム)とNon-preemptive
(たとえば、ackermannの関数)に分岐するデータ構造に関する本を読んでいました。著者は、この分岐の背後に適切な理由を与えずに、私が言及した2つの例のみを示しました。この分岐は、末尾および非末尾の再帰関数と同じですか?