Linq记忆问题

因为我对linq很新,所以我想在下面的例子中询问我的理解是否正确。

让我们假设我有非常大的动物名称集(100k记录),我想提交它们并以非常耗时的方法(2周)处理过滤的项目。 RunWithLinq()RunWithoutLinq()方法完全相同。

这是真的,使用第一种方法,原始(大)集合将在离开方法后留在内存中,并且不会被GC触及,而使用无linq方法时, GC将删除该集合?

我会感激一点解释。

 class AnimalProcessor { private IEnumerable animalsToProcess; internal AnimalProcessor(IEnumerable animalsToProcess) { this.animalsToProcess = animalsToProcess; } internal void Start() { //do sth for 2 weeks with the collection } } class Program { static void RunWithLinq() { var animals = new string[] { "cow", "rabbit", "newt", "ram" }; var filtered = from animal in animals where animal.StartsWith("ra") select animal; AnimalProcessor ap = new AnimalProcessor(filtered); ap.Start(); } static void RunWithoutLinq() { var animals = new string[] { "cow", "rabbit", "newt", "ram" }; var filtered = new List(); foreach (string animal in animals) if(animal.StartsWith("ra")) filtered.Add(animal); AnimalProcessor ap = new AnimalProcessor(filtered); ap.Start(); } } 

那么, animals将有资格在每种方法结束时收集,所以严格来说你的陈述是错误的。 在非LINQ案例中, animals很快就有资格收集,所以你的陈述的要点是正确的。

确实,每种内存的使用都不同。 但是,这里有一个暗示,LINQ在内存使用方面通常更差,而实际上它通常允许比采用其他类型的方法更好的内存使用(尽管有非LINQ方式做同样的事情LINQ方式,当我使用.NET2.0时,我非常喜欢这个特定问题的基本方法。

让我们先考虑两种方法,非LINQ:

 var animals = new string[] { "cow", "rabbit", "newt", "ram" }; var filtered = new List(); foreach (string animal in animals) //at this point we have both animals and filtered in memory, filtered is growing. if(animal.StartsWith("ra")) filtered.Add(animal); //at this point animals is no longer used. While still "in scope" to the source //code, it will be available to collection in the produced code. AnimalProcessor ap = new AnimalProcessor(filtered); //at this point we have filtered and ap in memory. ap.Start(); //at this point ap and filtered become eligible for collection. 

值得注意的是两件事。 一个“合格”的收集并不意味着收集将在那一刻发生,只是它可以在未来的任何时候。 二,如果对象仍然在范围内,如果它没有再次使用(甚至在某些情况下使用它,但这是另一个细节级别),则可能发生收集。 范围规则与程序源有关,是程序编写时可能发生的事情(程序员可以添加使用该对象的代码),GC集合资格规则与编译程序有关,并且是在编写程序(程序员可以添加这样的代码,但他们没有)。

现在让我们看一下LINQ案例:

 var animals = new string[] { "cow", "rabbit", "newt", "ram" }; var filtered = from animal in animals where animal.StartsWith("ra") select animal; // at this pint we have both animals and filtered in memory. // filtered defined as a class that acts upon animals. AnimalProcessor ap = new AnimalProcessor(filtered); // at this point we have ap, filtered and animals in memory. ap.Start(); // at this point ap, filtered and animals become eligible for collection. 

因此,在这种情况下,直到最后才能收集任何相关对象。

但请注意, filtered从不是一个大对象。 在第一种情况下, filtered的列表包含0到n个对象范围内的某个位置,其中n是animals的大小。 在第二种情况下, filtered是一个对象,它将根据需要对animals起作用,并且本身具有基本上恒定的记忆。

因此,非LINQ版本的峰值内存使用率更高,因为animals仍然存在并且filtered包含所有相关对象。 随着animals大小随着程序的变化而增加,实际上非LINQ版本最有可能首先出现严重的内存不足,因为在非LINQ情况下峰值内存使用状态更糟。

另一件需要考虑的事情是,在现实世界的情况下,我们有足够的项目来担心内存消耗,就像我们的源不会是一个列表。 考虑:

 IEnumerable getAnimals(TextReader rdr) { using(rdr) for(string line = rdr.ReadLine(); line != null; line = rdr.ReadLine()) yield return line; } 

此代码读取文本文件并一次返回每行。 如果每一行都保留了动物的名称,我们可以使用它来代替var animals作为filtered来源。

在这种情况下,虽然LINQ版本的内存使用非常少(一次只需要一个动物名称在内存中),而非LINQ版本具有更大的内存使用(将每个动物名称加载到“ra”中)进一步行动前的记忆)。 LINQ版本也将在最多几毫秒后开始处理,而非LINQ版本必须首先加载所有内容,然后才能完成一项工作。

因此,LINQ版本可以愉快地处理数十亿字节的数据,而不需要使用更多的内存来处理少量数据,而非LINQ版本则会遇到内存问题。

最后,重要的是要注意,这与LINQ本身没有任何关系,因为您使用LINQ的方法与没有LINQ的方法之间存在差异。 要使LINQ等效于非LINQ使用:

 var filtered = (from animal in animals where animal.StartsWith("ra") select animal).ToList(); 

使非LINQ等效于LINQ使用

 var filtered = FilterAnimals(animals); 

您还可以在其中定义:

 private static IEnumerable FilterAnimals(IEnumerable animals) { foreach(string animal in animals) if(animal.StartsWith("ra")) yield return animal; } 

哪个使用.NET 2.0技术,但是在创建从IEnumerable派生的对象时,即使使用.NET 1.1(尽管有更多代码)也可以这样做

基于LINQ的方法将原始集合保留在内存中,但不会存储带有过滤项的单独集合。

要更改此行为,请调用.ToList()

是的,这是正确的 – 因为filtered变量本质上是查询 ,而不是查询结果 。 迭代它将每次重新评估查询。

如果你想让它们相同,你可以调用ToList

 var filtered = animals.Where(animal => animal.StartsWith("ra")) .ToList(); 

(我已经将它从查询表达式语法转换为“点符号”,因为在这种情况下,它更简单。)