Como se mencionó en otras respuestas, CLR admite la optimización de llamadas de cola y parece que históricamente ha estado bajo mejoras progresivas. Pero admitirlo en C # tiene un Proposal
problema abierto en el repositorio de git para el diseño del lenguaje de programación C # Support tail recursion # 2544 .
Puede encontrar algunos detalles e información útiles allí. Por ejemplo @jaykrell mencionó
Déjame darte lo que sé.
A veces, el tailcall es una actuación en la que todos ganan. Puede ahorrar CPU. jmp es más barato que call / ret. Puede ahorrar pila. Tocar menos pila mejora la localidad.
A veces, tailcall es una pérdida de rendimiento, una ganancia de pila. El CLR tiene un mecanismo complejo en el que pasar más parámetros a la persona que llama de los que recibió la persona que llama. Me refiero específicamente a más espacio de pila para parámetros. Esto es lento. Pero conserva pila. Solo hará esto con la cola. prefijo.
Si los parámetros de la persona que llama son más grandes que los parámetros de la persona que llama, por lo general es una transformación de ganar-ganar bastante fácil. Puede haber factores como el cambio de posición de parámetro de administrado a entero / flotante, y generar StackMaps precisos y demás.
Ahora, hay otro ángulo, el de los algoritmos que exigen la eliminación del tailcall, con el fin de poder procesar datos arbitrariamente grandes con stack fijo / pequeño. No se trata de rendimiento, sino de capacidad para correr.
También déjeme mencionar (como información adicional), cuando estamos generando una lambda compilada usando clases de expresión en el System.Linq.Expressions
espacio de nombres, hay un argumento llamado 'tailCall' que, como se explica en su comentario, es
Un bool que indica si se aplicará la optimización de la llamada de cola al compilar la expresión creada.
Aún no lo probé y no estoy seguro de cómo puede ayudar en relación con su pregunta, pero probablemente alguien pueda probarlo y puede ser útil en algunos escenarios:
var myFuncExpression = System.Linq.Expressions.Expression.Lambda<Func< … >>(body: … , tailCall: true, parameters: … );
var myFunc = myFuncExpression.Compile();
preemptive
(por ejemplo, algoritmo factorial) yNon-preemptive
(por ejemplo, función de ackermann). El autor dio solo dos ejemplos que he mencionado sin dar un razonamiento adecuado detrás de esta bifurcación. ¿Es esta bifurcación lo mismo que las funciones recursivas de cola y no cola?