Domanda

Mi piace definire le sequenze in modo ricorsivo come segue:

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

Non sono sicuro se le sequenze ricorsive come questa debbano essere utilizzate nella pratica. Il rendimento! appare come ricorsivo di coda, ma non sono sicuro al 100% dal momento che viene chiamato dall'interno di un altro IEnumerable. Dal mio punto di vista, il codice crea un'istanza di IEnumerable su ogni chiamata senza chiuderla, il che renderebbe effettivamente anche questa memoria di perdita di funzione.

Questa funzione perderà memoria? Del resto è anche "coda ricorsiva"?

[Modifica da aggiungere]: sto armeggiando con NProf per una risposta, ma penso che sarebbe utile ottenere una spiegazione tecnica relativa all'implementazione di sequenze ricorsive su SO.

È stato utile?

Soluzione

Ora sono al lavoro, quindi sto guardando bit leggermente più recenti rispetto a Beta1, ma sulla mia scatola in modalità Rilascio e quindi 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
    }

genera codice MSIL quasi identico quando compilato in modalità 'Release'. E funzionano alla stessa velocità di questo codice C #:

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

(ad es. ho eseguito tutte e tre le versioni sulla mia scatola e stampato il milionesimo risultato, e ogni versione ha richiesto circa 1,3 secondi, +/- 1 secondi). (Non ho fatto alcun profilo di memoria; è possibile che mi manchi qualcosa di importante.)

In breve, non vorrei sudare troppo pensando a problemi come questo se non misuri e vedi un problema.

Modifica

Mi rendo conto di non aver veramente risposto alla domanda ... Penso che la risposta breve sia "no, non perde". (C'è un senso speciale in cui tutti gli "infiniti" IEnumerables (con un archivio di backup memorizzato nella cache) "perdono" (a seconda di come si definisce "perdita"), vedere

Evitare overflow dello stack (con sequenze infinite di sequenze F #)

per un'interessante discussione su IEnumerable (aka 'seq') contro LazyList e su come il consumatore può consumare avidamente LazyLists per "dimenticare" i vecchi risultati per prevenire un certo tipo di "perdita".

Altri suggerimenti

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

Le perdite di memoria in .NET di solito si presentano sotto forma di risorse non gestite che si stanno utilizzando nell'applicazione (connessioni al database, flussi di memoria, ecc.). Istanze come questa in cui crei più oggetti e poi li abbandoni non sono considerate una perdita di memoria poiché Garbage Collector è in grado di liberare memoria.

Non perderà memoria, genererà semplicemente una sequenza infinita, ma poiché le sequenze sono IEnumerables puoi enumerarle senza problemi 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 delle chiamate di coda può essere disabilitata per consentire il debug completo, ma in fase di rilascio non ci sarà alcun problema.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top