2009-06-19 4 views
10

Я хотел бы определить последовательности рекурсивно следующим образом:Рекурсивные последовательности утечки памяти?

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

Я не уверен, если рекурсивные последовательности, как это следует использовать на практике. yield!появляется, чтобы быть рекурсивным, но я не уверен на 100%, так как он вызван изнутри другого IEnumerable. С моей точки зрения, код создает экземпляр IEnumerable при каждом вызове, не закрывая его, что фактически заставило бы эту функцию также утечки памяти.

Будет ли эта функция утечкой памяти? В этом отношении даже «хвост рекурсивный»?

[Edit to add]: Я собираюсь ответить с NProf для ответа, но я думаю, что было бы полезно получить техническое объяснение относительно реализации рекурсивных последовательностей на SO.

+2

>> «К сожалению, у меня нет опыта профилирования, поэтому я могу Я не знаю ответа самостоятельно. Какая-то шутка? Как получить опыт? – user79755

+2

Для записи я сейчас смотрю NProf, но надеюсь получить быстрый ответ и техническое объяснение SO. – Juliet

ответ

7

Я сейчас на работе, так что я смотрю на несколько новых битов, чем Beta1, но на моем окне в режиме выпуска, а затем, глядя на скомпилированный код с .Net Reflector, кажется, что эти два

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 
    } 

генерировать почти идентичный код MSIL при компиляции в режиме «Release». И они работают примерно с той же скоростью, как этот C# код:

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

(например, я побежал все три версии на моей коробке и напечатал миллионную результат, и каждая версия занимает около 1.3s, +/- 1 сек). (Я не делал никакого профилирования памяти, возможно, у меня что-то не так важно.)

Короче говоря, я бы не стал слишком много думать о таких проблемах, если вы не измеряете и не видите проблему.

EDIT

Я понимаю, что на самом деле не ответить на этот вопрос ... Я думаю, короткий ответ «нет, это не утечка».(Существует особый смысл, в котором все «бесконечный» IEnumerables (с кэшированным резервным хранилищем) «утечка» (в зависимости от того, как вы определяете «утечку»), см

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

для интересного обсуждения IEnumerable (aka 'seq') по сравнению с LazyList и то, как потребитель может охотно потреблять LazyLists, чтобы «забыть» старые результаты, чтобы предотвратить определенную «утечку».)

+1

Еще раз спасибо, Брайан :) И еще слава остальной команде F # :) – Juliet

-1

Приложения .NET не «утечки» памяти таким образом. Даже если вы создаете много объектов, сбор мусора освободит любые объекты, которые не имеют корней для самого приложения.

Утечки памяти в .NET обычно поступают в виде неуправляемых ресурсов, которые вы используете в своем приложении (подключения к базе данных, потоки памяти и т. Д.). Такие экземпляры, в которых вы создаете несколько объектов, а затем оставляете их, не считаются утечкой памяти, так как сборщик мусора может освободить память.

+1

Сбор мусора не гарантирует, когда он собирается собирать эти объекты. Нет, это не практика утечки памяти, но это не хорошая практика экономии памяти. – marr75

+0

@ marr75 - Хорошая точка и хорошее различие! –

+1

Возможно также, что созданный IEnumerable может быть структурирован таким образом, чтобы более ранние элементы не стали пригодными для сбора мусора даже после того, как они больше не нужны, что я бы рассмотрел утечку сортов. – kvb

-1

Он не будет утечки памяти, он просто генерирует бесконечную последовательность, но поскольку последовательности являются IEnumerables, вы можете перечислить их без проблем памяти. Тот факт, что рекурсия происходит внутри функции генерации последовательности, не влияет на безопасность рекурсии. Просто имейте в виду, что в режиме отладки оптимизация хвостового вызова может быть отключена, чтобы обеспечить полную отладку, но в выпуске не будет никаких проблем.

+2

Это зависит от того, использует ли функция tail-recursion. Для сравнения, эквивалентный код C# будет вызывать исключение StackOverflowException после примерно 15000 целых чисел: static IEnumerable startFrom (int x) {yield return x; foreach (int y в startFrom (x + 1)) возвращает return y; } – Juliet

+1

Приложение: после быстрого теста выглядит так, что код F # * является хвостом рекурсивным. Thats хорошая новость :) – Juliet