Рекурсивные последовательности утечки памяти?

Мне нравится определять последовательности рекурсивно следующим образом:

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

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

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

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

Ответ 1

Теперь я нахожусь на работе, поэтому я смотрю несколько более новые бит, чем Beta1, но на моем ящике в режиме Release, а затем глядя на скомпилированный код с .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". И они работают примерно с такой же скоростью, как этот код С#:

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

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

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

ИЗМЕНИТЬ

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

Избегайте (с бесконечными последовательностями последовательности F #)

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

Ответ 2

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

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

Ответ 3

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