2009-06-19 10 views
10

Mi piace definire sequenze in modo ricorsivo come segue:Le sequenze ricorsive perdono memoria?

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

io non sono sicuro se le sequenze ricorsive come questo dovrebbero essere utilizzati nella pratica. Il yield!appare come coda ricorsiva, ma non sono sicuro al 100% dal momento che viene chiamato da un altro IEnumerable. Dal mio punto di vista, il codice crea un'istanza di IEnumerable su ogni chiamata senza chiuderla, il che in realtà renderebbe questa funzione anche una perdita di memoria.

Sarà questo perdita di memoria funzione? Del resto è anche "coda ricorsiva"?

[Modifica per aggiungere]: Sto cercando a tentoni con NProf per una risposta, ma penso che sarebbe utile ottenere una spiegazione tecnica riguardante l'implementazione di sequenze ricorsive su SO.

+2

>> "Purtroppo, non ho esperienza di profiler, quindi posso trovo la risposta da sola. " Una specie di scherzo? Come si fa esperienza? – user79755

+2

Per ora sto guardando NProf in questo momento, ma spero di ottenere una risposta più rapida e una spiegazione tecnica su SO. – Juliet

risposta

7

sono al lavoro ora così sto guardando bit leggermente più recenti di Beta1, ma sulla mia macchina in modalità di rilascio e poi guardando il codice compilato con NET Reflector, sembra che questi due

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 
    } 

generare codice MSIL quasi identico quando compilato in modalità "Rilascio". E corrono più o meno alla stessa velocità di questo codice C#:

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

(per esempio ho eseguito tutte e tre le versioni sulla mia macchina e stampato il risultato milionesima, e ogni versione ha richiesto circa 1.3s, +/- 1s). (Non ho fatto alcun profilo di memoria, è possibile che manchi qualcosa di importante.)

In breve, non vorrei troppo pensare a problemi come questo se non si misura e si vede un problema.

EDIT

mi rendo conto che non ho davvero rispondere alla domanda ... Penso che la risposta è "no, non perde".(C'è un senso speciale in cui tutti IEnumerables 'infinito' (con un negozio di supporto cache) 'fuga' (a seconda di come si definisce 'fuga'), vedi

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

per un'interessante discussione di IEnumerable (aka "seq") contro LazyList e in che modo il consumatore può consumare ardentemente LazyList per "dimenticare" vecchi risultati per evitare un certo tipo di "perdita".)

+1

Grazie ancora, Brian :) E complimenti anche per il resto del team F # :) – Juliet

-1

Le applicazioni .NET non "perdono" la memoria in questo modo. Anche se stai creando molti oggetti, una garbage collection libererà tutti gli oggetti che non hanno radici nell'applicazione stessa.

perdite di memoria in .NET sono generalmente in forma di risorse non gestite che si sta utilizzando nella vostra applicazione (connessioni al database, flussi di memoria, ecc.) Istanze come questa in cui crei più oggetti e poi li abbandoni non sono considerati una perdita di memoria poiché il garbage collector è in grado di liberare la memoria.

+1

La garbage collection non fornisce alcuna garanzia su quando sta per raccogliere questi oggetti, tuttavia. Quindi no, questa non è una pratica di perdita di memoria, ma non è neanche una buona pratica di economia della memoria. – marr75

+0

@ marr75 - Buon punto e buona distinzione! –

+1

È anche possibile che l'oggetto IEnumerable generato sia strutturato in modo che gli elementi precedenti non diventino idonei per la garbage collection anche dopo che non sono più necessari, cosa che considererei una perdita di sorta. – kvb

-1

E non perderà alcuna memoria, sarà solo generare una sequenza infinita, ma dal momento che le sequenze sono IEnumerables si possono elencare, senza preoccupazioni di memoria. Il fatto che la ricorsione avvenga all'interno della funzione di generazione della sequenza non influisce sulla sicurezza della ricorsione. Ricorda che in modalità debug l'ottimizzazione della chiamata di coda può essere disabilitata per consentire il debug completo, ma nel rilascio non ci saranno problemi di sorta.

+2

Dipende se la funzione utilizza la ricorsione in coda. Per confronto, il codice C# equivalente getterà una StackOverflowException dopo circa 15000 numeri interi: statico IEnumerable startFrom (int x) {yield return x; foreach (int y in startFrom (x + 1)) return return y; } – Juliet

+1

Addendum: dopo un rapido test, sembra che il codice F # * sia ricorsivo in coda. Questa è una buona notizia :) – Juliet