2009-03-04 47 views
16

¿Existen reglas generales al usar la recursión para evitar los desbordamientos de pila?Uso de recursividad en C#

+0

¿Alguna vez hay alguna regla fija en la programación? – Jason

+0

punto justo- Cambié la pregunta de "conjunto duro" a "general" –

+0

No específica de C#. Cualquier regla se aplicaría a todos los idiomas. –

Respuesta

32

¿Cuántas veces usted será capaz de recursivo dependerá de:

  • El tamaño de la pila (que suele ser de 1 MB IIRC, pero el binario puede ser editado a mano, yo no recomendaría hacerlo)
  • ¿Cuánto pila cada nivel de los usos de recursividad (un método con 10 Guid variables locales no capturadas será tarda más pila que un método que no tiene ningún variables locales, por ejemplo)
  • El JIT que está utilizando - a veces el JIT va a usar recursividad de cola, otras veces no lo hará . Las reglas son complicadas y no puedo recordarlas. (Hay una blog post by David Broman back from 2007 y an MSDN page from the same author/date, pero pueden estar fuera de fecha por ahora.)

¿Cómo evitar desbordamientos de pila? No recurse demasiado lejos :) Si no puede estar razonablemente seguro de que su recursividad terminará sin ir muy lejos (me preocuparía "más de 10", aunque eso es muy seguro), vuelva a escribir para evitar la recursión.

+0

Creo que el TCO solo ocurre en el IIRC de 64 bits, para todos los demás casos, necesita prefijar la llamada con cola. Y si no juegas con las reglas, es posible que ni siquiera se llamen. – leppie

+0

@leppie: Exactamente el tipo de reglas que no recordaba :) Veré si puedo desenterrar la publicación del blog en él. –

+0

@Jon aquí hay uno que encontré: http://blogs.msdn.com/davbr/pages/tail-call-jit-conditions.aspx –

1

Aparte de tener un tamaño de pila razonable y asegurarse de dividir y conquistar su problema de manera que trabaje continuamente en un problema menor, realmente no.

7

No conozco ningún conjunto duro para evitar stackoverflows. Personalmente, intento asegurar -
1. Tengo mis fundas de base correctas.
2. El código llega al caso base en algún momento.

+0

Ok Ted. En funciones recursivas, hay dos partes -1. La parte que reduce el problema y se llama - caso recursivo. 2. La parte que representa la instancia más pequeña del problema, que tiene una (generalmente) respuesta trivial -base de casos. – batbrat

+0

La llamada recursiva finaliza y comienza a rebobinar cuando se alcanza un caso base. Intentaba decir que la condición base debería alcanzarse en algún momento. – batbrat

+0

http://en.wikipedia.org/wiki/Recursion_(computer_science) es un buen lugar para buscar. – batbrat

4

Si se encuentra generando tantos marcos de pila, puede considerar desenrollar su recursividad en un bucle.

Especialmente si está haciendo múltiples niveles de recursión (A-> B-> C-> A-> B ...) puede encontrar que puede extraer uno de esos niveles en un bucle y ahorrarse un poco de memoria .

+0

+1, buena respuesta. He visto el análisis de árbol hecho en un bucle. Fue mucho más rápido y apilado seguro. –

3

El límite normal, si no queda mucho en la pila entre llamadas sucesivas, es de alrededor de 15000-25000 niveles de profundidad. 25% de eso si tienes IIS 6+.

La mayoría de los algoritmos recursivos se pueden expresar de forma iterativa.

Hay varias maneras de aumentar el espacio asignado en la pila, pero prefiero que primero encuentre una versión iterativa. :)

8

Realmente depende del algoritmo recursivo que estés usando. Si se trata de la repetición simple, puede hacer algo como esto:

public int CalculateSomethingRecursively(int someNumber) 
{ 
    return doSomethingRecursively(someNumber, 0); 
} 

private int doSomethingRecursively(int someNumber, int level) 
{ 
    if (level >= MAX_LEVEL || !shouldKeepCalculating(someNumber)) 
     return someNumber; 
    return doSomethingRecursively(someNumber, level + 1); 
} 

Vale la pena señalar que este enfoque es realmente sólo es útil cuando el nivel de recursividad se puede definir como un límite lógico. En el caso de que esto no pueda ocurrir (como un algoritmo de dividir y conquistar), tendrá que decidir cómo desea equilibrar la simplicidad con el rendimiento frente a las limitaciones de recursos. En estos casos, es posible que tenga que cambiar de método una vez que haya alcanzado el límite predefinido de la arquitectura. Un medio eficaz de hacer esto que he usado en el algoritmo de la solución rápida es hacerlo como una proporción del tamaño total de la lista. En este caso, el límite lógico es el resultado de cuando las condiciones ya no son óptimas.

+0

+1 - iba a decir lo mismo –

+0

Aunque me gusta esta solución, ¿no dependería de la máquina? algunos sistemas podrían manejar más niveles, y no hay forma (no forma simple) de determinarlo en tiempo de ejecución. Además, si NECESITAS tantos marcos de pila, estarías paralizando el programa con esto. – DevinB

+0

Tiene razón, pero yo diría que el límite debe ser lógico (basado en el algoritmo), no físico (basado en las capacidades de la máquina). De lo contrario, vas a golpear la pared si alguna vez se paraleliza. –

0

Recuerde, si usted tiene que preguntar acerca de los límites del sistema, entonces es probable que esté haciendo algo muy mal.

Por lo tanto, si cree que podría obtener un desbordamiento de pila en el funcionamiento normal, entonces debe pensar en un enfoque diferente del problema.

No es difícil convertir una función recursiva en iterativa, especialmente porque C# tiene la colección Generic :: Stack. El uso del tipo de pila mueve la memoria utilizada en el montón del programa en lugar de la pila. Esto le proporciona el rango completo de direcciones para almacenar los datos recursivos. Si eso no es suficiente, no es demasiado difícil colocar los datos en el disco. Pero consideraría seriamente otras soluciones si llega a esta etapa.

+1

No estoy preguntando sobre los límites del sistema, solo intento obtener un poco de conocimiento ... –

1

El tamaño de pila predeterminado para un hilo es de 1 MB, si se está ejecutando con el CLR predeterminado. Sin embargo, otros hosts pueden cambiar eso. Por ejemplo, el host ASP cambia el valor predeterminado a 256 KB. Esto significa que puede tener un código que se ejecute perfectamente bajo VS, pero que se rompa cuando lo implemente en el entorno de alojamiento real.

Afortunadamente puede especificar un tamaño de pila, cuando crea una nueva cadena utilizando el constructor correcto. En mi experiencia, rara vez es necesario, pero he visto un caso donde esta era la solución.

Puede editar el encabezado PE del binario para cambiar el tamaño predeterminado. Esto es útil si quiere cambiar el tamaño del hilo principal. De lo contrario, recomendaría usar el constructor apropiado al crear hilos.

1

Escribí un breve artículo sobre este here. Básicamente, paso un parámetro opcional llamado profundidad, que le agrega 1 cada vez que profundizo en él. Dentro del método recursivo, verifico la profundidad de un valor. Si es mayor que el valor que configuro, arrojo una excepción. El valor (umbral) dependería de las necesidades de su aplicación.