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

StackOverflow https://stackoverflow.com/questions/1019945

Вопрос

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

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

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

Будет ли эта функция вызывать утечку памяти?Если уж на то пошло, это вообще «хвостовая рекурсия»?

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

Это было полезно?

Решение

Я сейчас на работе, поэтому смотрю на немного более новые версии, чем 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 с).(Я не делал никакого профилирования памяти;возможно я упускаю что-то важное.)

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

РЕДАКТИРОВАТЬ

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

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

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

Другие советы

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top