Wie bereits erwähnt, unterstützt CLR die Tail-Call-Optimierung und scheint historisch gesehen schrittweise verbessert worden zu sein. Die Unterstützung in C # hat jedoch ein offenes Proposal
Problem im Git-Repository für das Design der C # -Programmiersprache Support Tail Recursion # 2544 .
Dort finden Sie einige nützliche Details und Informationen. Zum Beispiel @jaykrell erwähnt
Lass mich geben, was ich weiß.
Manchmal ist Tailcall eine Win-Win-Leistung. Es kann CPU sparen. jmp ist billiger als call / ret Es kann Stack sparen. Wenn Sie weniger Stapel berühren, wird die Lokalität verbessert.
Manchmal ist Tailcall ein Leistungsverlust, Stack Win. Die CLR verfügt über einen komplexen Mechanismus, mit dem mehr Parameter an den Angerufenen übergeben werden können, als der Anrufer erhalten hat. Ich meine speziell mehr Stapelplatz für Parameter. Das ist langsam. Aber es spart Stapel. Dies geschieht nur mit dem Schwanz. Präfix.
Wenn die Aufruferparameter stapelgroß als die Angerufenenparameter sind, ist dies normalerweise eine ziemlich einfache Win-Win-Transformation. Es kann Faktoren wie die Änderung der Parameterposition von verwaltet zu Integer / Float und die Generierung präziser StackMaps und dergleichen geben.
Nun gibt es einen anderen Winkel, nämlich den von Algorithmen, die eine Tailcall-Eliminierung erfordern, um beliebig große Daten mit festem / kleinem Stapel verarbeiten zu können. Hier geht es nicht um Leistung, sondern um die Fähigkeit, überhaupt zu laufen.
Lassen Sie mich auch erwähnen (als zusätzliche Information): Wenn wir ein kompiliertes Lambda unter Verwendung von Ausdrucksklassen im System.Linq.Expressions
Namespace generieren , gibt es ein Argument namens 'tailCall', das, wie in seinem Kommentar erläutert, es ist
Ein Bool, das angibt, ob beim Kompilieren des erstellten Ausdrucks die Tail-Call-Optimierung angewendet wird.
Ich habe es noch nicht ausprobiert und bin mir nicht sicher, wie es im Zusammenhang mit Ihrer Frage helfen kann, aber wahrscheinlich kann es jemand versuchen und kann in einigen Szenarien nützlich sein:
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
aufteilt, nämlich (z. B. Fakultätsalgorithmus) undNon-preemptive
(z. B. Ackermann-Funktion). Der Autor gab nur zwei Beispiele an, die ich erwähnt habe, ohne eine angemessene Begründung für diese Gabelung zu geben. Ist diese Gabelung gleichbedeutend mit rekursiven Funktionen für Schwanz und Nicht-Schwanz?