2009-06-19 8 views
10

Ich mag Sequenzen rekursiv wie folgt definieren:Führen rekursive Sequenzen Speicherverlust aus?

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

Ich bin nicht sicher, ob rekursive Sequenzen wie diese sollten in der Praxis verwendet werden. Die yield!erscheint, um rekursiv zu sein, aber ich bin nicht 100% sicher, da es von einem anderen IEnumerable aufgerufen wird. Aus meiner Sicht erstellt der Code bei jedem Aufruf eine Instanz von IEnumerable, ohne sie zu schließen, wodurch diese Funktion tatsächlich ebenfalls Speicher verliert.

Wird diese Funktion Leckspeicher? Ist es überhaupt "rekursiv"?

[Bearbeiten, um hinzuzufügen]: Ich fummle herum mit Nprof für eine Antwort, aber ich denke, es wäre hilfreich, eine technische Erklärung über die Implementierung von rekursiven Sequenzen auf SO zu erhalten.

+2

>> "Leider habe ich keine Profiler-Erfahrung, also kann ich Finde die Antwort nicht selbst heraus. " Irgendeine Art von Witz? Wie bekommt man Erfahrung? – user79755

+2

Für die Aufnahme schaue ich gerade auf Nprof, aber ich hoffe, eine schnellere Antwort und eine technische Erklärung über SO zu bekommen. – Juliet

Antwort

7

ich bei der Arbeit bin jetzt so bei etwas neuere Bits Ich bin auf der Suche als Beta1, aber auf meiner Box in Release-Modus und dann auf den kompilierten Code mit .NET Reflector suchen, scheint es, dass diese beiden

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 
    } 

fast identische MSIL-Code generieren, wenn im Modus 'Release' kompiliert. Und sie laufen bei etwa der gleichen Geschwindigkeit wie dieser C# -Code:

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

(zum Beispiel lief ich alle drei Versionen auf meiner Box und druckte das millionste Ergebnis und jede Version dauerte etwa 1,3 s, +/- 1 s). (Ich habe kein Speicherprofil erstellt; es ist möglich, dass mir etwas Wichtiges fehlt.)

Kurz gesagt, würde ich nicht zu sehr darüber schwitzen, über solche Probleme nachzudenken, es sei denn, Sie messen und sehen ein Problem.

EDIT

Ich weiß, ich nicht wirklich die Frage beantworten haben ... Ich denke, die kurze Antwort ist „Nein, ist es nicht auslaufen“.(Es gibt einen speziellen Sinn, in dem alle ‚unendlich‘ IEnumerables (mit einem Cache gespeicherten Sicherungsspeicher) ‚Leck‘ (je nachdem, wie Sie definieren ‚Leck‘), siehe

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

für eine interessante Diskussion über IEnumerable (aka 'seq') gegen LazyList und wie der Verbraucher LazyLists eifrig konsumieren kann, um alte Ergebnisse zu "vergessen", um eine bestimmte Art von "Leck" zu verhindern.)

+1

Danke nochmal Brian :) Und ein großes Lob an den Rest des F # Teams :) – Juliet

-1

.NET-Anwendungen "lecken" nicht den Speicher auf diese Weise. Selbst wenn Sie viele Objekte erstellen, werden durch eine Garbage-Collection alle Objekte ohne Root-Rechte für die Anwendung selbst freigegeben.

Speicherverluste in .NET treten normalerweise in Form von nicht verwalteten Ressourcen auf, die Sie in Ihrer Anwendung verwenden (Datenbankverbindungen, Speicherstreams usw.). Instanzen wie diese, in denen Sie mehrere Objekte erstellen und dann auf sie verzichten, gelten nicht als Speicherverlust, da der Garbage Collector den Speicher freigeben kann.

+1

Die Speicherbereinigung gibt jedoch keine Garantie dafür, wann diese Objekte gesammelt werden. Also nein, das ist keine Speicherleck-Praxis, aber es ist auch keine gute Speicherökonomie-Praxis. – marr75

+0

@ marr75 - Guter Punkt und gute Unterscheidung! –

+1

Es ist auch möglich, dass das IEnumerable, das generiert wird, so strukturiert sein kann, dass frühere Elemente nicht für die Garbage Collection geeignet sind, selbst wenn sie nicht mehr benötigt werden. – kvb

-1

Es wird keinen Speicher verlieren, es wird nur eine unendliche Sequenz generiert, aber da Sequenzen IEnumerables sind, können Sie sie ohne Speicherprobleme aufzählen. Die Tatsache, dass die Rekursion innerhalb der Sequenzgenerierungsfunktion stattfindet, hat keinen Einfluss auf die Sicherheit der Rekursion. Bedenken Sie, dass im Debug-Modus die Tail-Call-Optimierung deaktiviert sein kann, um ein vollständiges Debugging zu ermöglichen, aber in der Veröffentlichung wird es überhaupt kein Problem geben.

+2

Es hängt davon ab, ob die Funktion Tail-Rekursion verwendet. Zum Vergleich wird der äquivalente C# -Code eine StackOverflowException nach ungefähr 15000 Ganzzahlen auslösen: statisch IEnumerable startFrom (int x) {yield return x; foreach (int y in startFrom (x + 1)) Rendite zurück y; } – Juliet

+1

Nachtrag: Nach einem schnellen Test sieht es aus wie der F # -Code * ist * tail rekursiv. Das sind gute Neuigkeiten :) – Juliet

Verwandte Themen