2009-06-19 15 views
10

me gusta definir secuencias recursivamente de la siguiente manera:¿Las secuencias recursivas pierden memoria?

let rec startFrom x = 
    seq { 
     yield x; 
     yield! startFrom (x + 1) 
    } 

No estoy seguro de si las secuencias recursivas como este deben ser utilizados en la práctica. El yield!aparece como cola recursiva, pero no estoy 100% seguro ya que se llama desde dentro de otro IEnumerable. Desde mi punto de vista, el código crea una instancia de IEnumerable en cada llamada sin cerrarla, lo que realmente haría que esta función también perdiera memoria.

¿Esta función perderá la memoria? Para el caso, ¿es incluso "cola recursiva"?

[Editar para añadir]: Estoy buscando a tientas NProf para obtener una respuesta, pero creo que sería útil obtener una explicación técnica sobre la implementación de secuencias recursivas en SO.

+2

>> "Desafortunadamente, no tengo experiencia en el perfil, entonces puedo 't encontrar la respuesta por mi cuenta ". ¿Algún tipo de broma? ¿Cómo se obtiene experiencia? – user79755

+2

Para el registro estoy viendo NProf en este momento, pero esperando obtener una respuesta más rápida y una explicación técnica sobre SO. – Juliet

Respuesta

7

estoy en el trabajo ahora, así que estoy buscando en trozos ligeramente más recientes que Beta1, pero en mi caja en modo de lanzamiento y luego mirar el código compilado con .Net reflector, parece que estos dos

let rec startFromA x =  
    seq {   
     yield x  
     yield! startFromA (x + 1)  
    } 

let startFromB x =  
    let z = ref x 
    seq {   
     while true do 
      yield !z 
      incr z 
    } 

generan un código MSIL casi idéntico cuando se compilan en el modo 'Liberar'. Y corren aproximadamente a la misma velocidad que el código C#:

public class CSharpExample 
{ 
    public static IEnumerable<int> StartFrom(int x) 
    { 
     while (true) 
     { 
      yield return x; 
      x++; 
     } 
    } 
} 

(por ejemplo, me encontré con las tres versiones en mi caja y el resultado impreso un millón, y cada versión tomó alrededor de 1.3s, 1s +/-). (No hice ningún perfil de memoria, es posible que me pierda algo importante.)

En resumen, no me preocupo demasiado por cuestiones como esta a menos que mida y vea un problema.

EDITAR

Me di cuenta que en realidad no responder a la pregunta ... Creo que la respuesta corta es "no, no se escapa".(Hay una sensación especial en el que todos los IEnumerables 'infinito' (con un almacén de respaldo en caché) 'fugas' (dependiendo de cómo se defina 'fuga'), ver

Avoiding stack overflow (with F# infinite sequences of sequences)

para una discusión interesante de IEnumerable (también conocido como 'seq') versus LazyList y cómo el consumidor puede consumir ansiosamente LazyLists para 'olvidar' resultados anteriores para evitar cierto tipo de 'fuga'.)

+1

Gracias, otra vez, Brian :) Y felicitaciones al resto del equipo F # también :) – Juliet

-1

. Las aplicaciones .NET no "pierden" memoria de esta manera. Incluso si está creando muchos objetos, una recolección de basura liberará cualquier objeto que no tenga ninguna raíz en la aplicación.

Las fugas de memoria en .NET suelen venir en forma de recursos no administrados que está utilizando en su aplicación (conexiones de bases de datos, flujos de memoria, etc.). Las instancias como esta en las que se crean varios objetos y luego se abandonan no se consideran una pérdida de memoria, ya que el recolector de basura puede liberar la memoria.

+1

Sin embargo, la recolección de basura no garantiza cuándo va a recoger estos objetos. Así que no, esta no es una práctica de pérdida de memoria, pero tampoco es una buena práctica de economía de memoria. – marr75

+0

@ marr75 - Buen punto y buena distinción! –

+1

También es posible que el IEnumerable que se genere se pueda estructurar para que los elementos anteriores no sean elegibles para la recolección de basura incluso después de que ya no se necesiten, lo que consideraría una especie de fuga. – kvb

-1

No perderá ninguna memoria, solo generará una secuencia infinita, pero como las secuencias son IEnumerables, puede enumerarlas sin problemas de memoria. El hecho de que la recursión ocurra dentro de la función de generación de secuencia no afecta la seguridad de la recursión. Simplemente tenga en cuenta que en el modo de depuración, la optimización de la cola de llamada se puede deshabilitar para permitir la depuración completa, pero en la versión no habrá ningún problema.

+2

Depende de si la función usa tail-recursión. A modo de comparación, el código C# equivalente arrojará una StackOverflowException después de alrededor de 15000 enteros: static IEnumerable startFrom (int x) {yield return x; foreach (int y en startFrom (x + 1)) produce return y; } – Juliet

+1

Adición: después de una prueba rápida, parece que el código F # * es * tail recursive. Esas son buenas noticias :) – Juliet

Cuestiones relacionadas