10

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

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

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

这个函数会泄漏内存吗?就此而言,它甚至是“尾递归”吗?

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

4

3 回答 3

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++;
        }
    }
}

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

简而言之,除非您衡量并看到问题,否则我不会过多思考此类问题。

编辑

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

避免堆栈溢出(使用 F# 无限序列)

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

于 2009-06-19T21:25:46.923 回答
-2

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

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

于 2009-06-19T20:53:24.007 回答
-2

它不会泄漏任何内存,它只会生成一个无限序列,但由于序列是 IEnumerables,因此您可以枚举它们而无需担心内存问题。递归发生在序列生成函数内部这一事实不会影响递归的安全性。请注意,在调试模式下,可能会禁用尾调用优化以允许完全调试,但在发布时不会有任何问题。

于 2009-06-19T20:55:33.287 回答