我喜欢递归地定义序列,如下所示:
let rec startFrom x =
seq {
yield x;
yield! startFrom (x + 1)
}
我不确定这样的递归序列是否应该在实践中使用。 yield!
出现是尾递归的,但我不是100%肯定,因为它是从另一个IEnumerable内部调用的。从我的角度来看,代码在每次调用时都会创建一个IEnumerable实例而不关闭它,这实际上会使这个函数泄漏内存。
此功能会泄漏内存吗?就此而言,它甚至是“尾递归”?
[编辑添加]:我正在寻找NProf的答案,但我认为在SO上获得关于递归序列实现的技术解释会有所帮助。
答案 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,你可以枚举它们而不需要内存。 递归发生在序列生成函数内部的事实不会影响递归的安全性。 请记住,在调试模式下,可以禁用尾部调用优化以便进行完全调试,但在发布时不会出现任何问题。