Есть ли у рекурсивных последовательностей утечка памяти? - PullRequest
10 голосов
/ 20 июня 2009

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

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

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

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

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

Ответы [ 3 ]

7 голосов
/ 20 июня 2009

Я сейчас на работе, поэтому я смотрю на немного более новые биты, чем 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». И они работают примерно с той же скоростью, что и этот код C #:

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

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

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

EDIT

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

Предотвращение переполнения стека (с F # бесконечными последовательностями последовательностей)

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

0 голосов
/ 20 июня 2009

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

0 голосов
/ 20 июня 2009

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

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

...