递归序列会泄漏内存吗?

时间:2009-06-19 20:39:15

标签: f# memory-leaks sequence tail-recursion

我喜欢递归地定义序列,如下所示:

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

我不确定这样的递归序列是否应该在实践中使用。 yield! 出现是尾递归的,但我不是100%肯定,因为它是从另一个IEnumerable内部调用的。从我的角度来看,代码在每次调用时都会创建一个IEnumerable实例而不关闭它,这实际上会使这个函数泄漏内存。

此功能会泄漏内存吗?就此而言,它甚至是“尾递归”?

[编辑添加]:我正在寻找NProf的答案,但我认为在SO上获得关于递归序列实现的技术解释会有所帮助。

3 个答案:

答案 0 :(得分: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代码。它们的运行速度与此C#代码大致相同:

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

(例如,我在我的盒子上运行了所有三个版本并打印了第一百万个结果,每个版本花了大约1.3s,+ / - 1s)。 (我没有做任何记忆分析;我可能会遗漏一些重要的东西。)

简而言之,除非你衡量和看到问题,否则我不会为这样的问题而过多思考。

修改

我意识到我并没有真正回答这个问题......我认为简短的回答是“不,它不泄漏”。 (有一种特殊的感觉,所有'无限'IEnumerables(带有缓存的后备存储)'泄漏'(取决于你如何定义'泄漏'),参见

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

有关IEnumerable(又名'seq')与LazyList的有趣讨论以及消费者如何急切地使用LazyLists来“忘记”旧结果以防止某种“泄漏”。)

答案 1 :(得分:-2)

.NET应用程序不会以这种方式“泄漏”内存。即使您正在创建许多对象,垃圾收集也会将任何没有根的对象释放到应用程序本身。

.NET中的内存泄漏通常以您在应用程序中使用的非托管资源的形式出现(数据库连接,内存流等)。像这样创建多个对象然后放弃它们的实例不被认为是内存泄漏,因为垃圾收集器能够释放内存。

答案 2 :(得分:-2)

它不会泄漏任何内存,它只会生成一个无限序列,但由于序列是IEnumerables,你可以枚举它们而不需要内存。 递归发生在序列生成函数内部的事实不会影响递归的安全性。 请记住,在调试模式下,可以禁用尾部调用优化以便进行完全调试,但在发布时不会出现任何问题。