Come altre risposte menzionate, CLR supporta l'ottimizzazione delle chiamate di coda e sembra che storicamente fosse in progressivo miglioramento. Ma supportarlo in C # ha un Proposal
problema aperto nel repository git per la progettazione del linguaggio di programmazione C # Support tail recursion # 2544 .
Puoi trovare alcuni dettagli e informazioni utili lì. Ad esempio @jaykrell menzionato
Fammi dare quello che so.
A volte tailcall è una performance vincente. Può risparmiare CPU. jmp è più economico di call / ret Può salvare lo stack. Toccando meno pila si migliora la località.
A volte tailcall è una perdita di prestazioni, stack win. Il CLR ha un meccanismo complesso in cui passare più parametri al chiamato rispetto a quelli ricevuti dal chiamante. Intendo specificamente più spazio nello stack per i parametri. Questo è lento. Ma conserva lo stack. Lo farà solo con la coda. prefisso.
Se i parametri del chiamante sono più grandi dello stack dei parametri del chiamato, di solito si tratta di una trasformazione win-win piuttosto semplice. Potrebbero esserci fattori come il cambiamento della posizione del parametro da gestito a intero / float e la generazione di StackMap precisi e simili.
Ora, c'è un altro angolo, quello degli algoritmi che richiedono l'eliminazione della tailcall, allo scopo di essere in grado di elaborare dati arbitrariamente grandi con stack fisso / piccolo. Non si tratta di prestazioni, ma di capacità di correre.
Consentitemi anche di menzionare (come informazioni aggiuntive), quando generiamo un lambda compilato utilizzando classi di espressioni nello System.Linq.Expressions
spazio dei nomi, c'è un argomento chiamato 'tailCall' che, come spiegato nel suo commento, è
Un valore bool che indica se l'ottimizzazione della chiamata di coda verrà applicata durante la compilazione dell'espressione creata.
Non l'ho ancora provato e non sono sicuro di come possa aiutare in relazione alla tua domanda, ma probabilmente qualcuno può provarlo e potrebbe essere utile in alcuni scenari:
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
(ad es. Algoritmo fattoriale) eNon-preemptive
(ad es. Funzione di ackermann). L'autore ha fornito solo due esempi che ho citato senza fornire un ragionamento appropriato dietro questa biforcazione. Questa biforcazione è uguale alle funzioni ricorsive tail e non-tail?